-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathThreadDemo.java
More file actions
288 lines (257 loc) · 11.3 KB
/
ThreadDemo.java
File metadata and controls
288 lines (257 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/**
* Simple Demonstration of Multithreading Concepts
*
* This class shows practical examples of thread usage with detailed explanations.
* Perfect for beginners who want to understand the basics of multithreading in Java.
*
* WHAT YOU'LL LEARN:
* 1. Race Conditions - What happens when threads access shared data without protection
* 2. Synchronization - How to fix race conditions and make code thread-safe
* 3. Thread Interruption - How to stop threads gracefully
*
* KEY TAKEAWAYS:
* - Always protect shared data when multiple threads access it
* - Use synchronized methods or blocks to prevent race conditions
* - Handle InterruptedException properly
* - Test your multithreaded code multiple times - bugs can be intermittent!
*/
public class ThreadDemo {
public static void main(String[] args) {
System.out.println("=== Thread Demo ===\n");
// Example 1: Simple counter with race condition
demonstrateRaceCondition();
// Example 2: Fixed race condition with synchronization
demonstrateSynchronizedCounter();
// Example 3: Thread interruption
demonstrateThreadInterruption();
}
/**
* Demonstrates race condition when multiple threads access shared data
*
* WHAT IS A RACE CONDITION?
* A race condition occurs when multiple threads access shared data simultaneously
* and the final result depends on the timing of their execution.
*
* WHY DOES THIS HAPPEN?
* The operation "counter++" is actually three steps:
* 1. Read the current value
* 2. Add 1 to it
* 3. Write the new value back
*
* If two threads do this at the same time, they might both read the same value,
* both add 1, and both write back the same result - losing one increment!
*
* RUN THIS MULTIPLE TIMES:
* You might get different results each time because the timing varies.
* This is why multithreading bugs are so hard to find and fix!
*/
private static void demonstrateRaceCondition() {
System.out.println("1. Race Condition Example:");
System.out.println(" (This may show inconsistent results due to race condition)");
System.out.println(" (Run this multiple times - you might get different results!)\\n");
// Create a counter that will be shared between threads
Counter counter = new Counter();
// Create 3 threads that will all try to increment the same counter
Thread[] threads = new Thread[3];
for (int i = 0; i < 3; i++) {
final int threadId = i; // Need final for lambda
threads[i] = new Thread(() -> {
// Each thread will increment the counter 1000 times
for (int j = 0; j < 1000; j++) {
// ===== UNSAFE INCREMENT =====
// This method is NOT synchronized - race condition possible!
counter.incrementUnsafe();
}
System.out.println(" Thread " + threadId + " finished incrementing");
});
}
// Start all 3 threads at the same time
for (Thread thread : threads) {
thread.start();
}
// Wait for all threads to complete
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// Display the results
System.out.println(" Expected: 3000 (3 threads × 1000 increments each)");
System.out.println(" Actual: " + counter.getCount());
System.out.println(" (If actual < 3000, we have a race condition!)");
System.out.println();
}
/**
* Demonstrates how synchronization fixes race condition
*
* WHAT IS SYNCHRONIZATION?
* Synchronization ensures that only one thread can access a piece of code at a time.
* It's like having a lock on a door - only one person can go through at a time.
*
* HOW DOES IT WORK?
* - When a thread enters a synchronized method, it gets a "lock"
* - Other threads must wait until the lock is released
* - This prevents multiple threads from modifying the same data simultaneously
*
* SYNCHRONIZED KEYWORD:
* - Can be used on methods: public synchronized void method()
* - Can be used on blocks: synchronized(object) { ... }
* - Only one thread can execute synchronized code at a time
*
* PERFORMANCE NOTE:
* Synchronization has a small performance cost, but it's usually worth it
* to prevent data corruption and ensure correct results.
*/
private static void demonstrateSynchronizedCounter() {
System.out.println("2. Synchronized Counter Example:");
System.out.println(" (This should always show correct results)");
System.out.println(" (Synchronization prevents race conditions!)\\n");
// Create a new counter (reset to 0)
Counter counter = new Counter();
// Create 3 threads that will increment the synchronized counter
Thread[] threads = new Thread[3];
for (int i = 0; i < 3; i++) {
final int threadId = i;
threads[i] = new Thread(() -> {
// Each thread will increment the counter 1000 times
for (int j = 0; j < 1000; j++) {
// ===== SAFE INCREMENT =====
// This method IS synchronized - no race condition possible!
counter.incrementSafe();
}
System.out.println(" Thread " + threadId + " finished incrementing");
});
}
// Start all 3 threads at the same time
for (Thread thread : threads) {
thread.start();
}
// Wait for all threads to complete
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// Display the results
System.out.println(" Expected: 3000 (3 threads × 1000 increments each)");
System.out.println(" Actual: " + counter.getCount());
System.out.println(" (Should always be exactly 3000 with synchronization!)");
System.out.println();
}
/**
* Demonstrates thread interruption
*
* WHAT IS THREAD INTERRUPTION?
* Thread interruption is a way to politely ask a thread to stop what it's doing.
* It's like knocking on someone's door to get their attention.
*
* HOW DOES IT WORK?
* - One thread calls interrupt() on another thread
* - The interrupted thread should check isInterrupted() regularly
* - If interrupted during sleep/wait, InterruptedException is thrown
* - The thread should clean up and exit gracefully
*
* WHY IS THIS IMPORTANT?
* - Allows threads to stop gracefully instead of being killed
* - Prevents resource leaks and data corruption
* - Enables proper cleanup of resources
* - Makes programs more responsive to user requests
*
* BEST PRACTICES:
* - Always check isInterrupted() in long-running loops
* - Handle InterruptedException properly
* - Call Thread.currentThread().interrupt() to restore interrupt status
* - Clean up resources before exiting
*/
private static void demonstrateThreadInterruption() {
System.out.println("3. Thread Interruption Example:");
System.out.println(" (Watch how the worker thread responds to interruption)\\n");
// Create a worker thread that does some work
Thread workerThread = new Thread(() -> {
System.out.println(" Worker thread started");
// Keep working until interrupted
while (!Thread.currentThread().isInterrupted()) {
System.out.println(" Worker thread is running...");
try {
// Sleep for 500ms - this can be interrupted
Thread.sleep(500);
} catch (InterruptedException e) {
// ===== HANDLE INTERRUPTION =====
System.out.println(" Worker thread was interrupted during sleep!");
// Restore the interrupt status (important!)
Thread.currentThread().interrupt();
break; // Exit the loop
}
}
System.out.println(" Worker thread finished gracefully");
});
// Start the worker thread
workerThread.start();
try {
// Let the worker thread run for 2 seconds
System.out.println(" Main thread: Letting worker run for 2 seconds...");
Thread.sleep(2000);
// Now interrupt the worker thread
System.out.println(" Main thread: Interrupting worker thread...");
workerThread.interrupt();
// Wait for the worker thread to finish
workerThread.join();
} catch (InterruptedException e) {
// If the main thread is interrupted, handle it
Thread.currentThread().interrupt();
}
System.out.println(" Main thread: Worker thread has finished");
System.out.println();
}
/**
* Simple counter class to demonstrate race conditions
*
* This class has two methods for incrementing a counter:
* 1. incrementUnsafe() - NOT synchronized, can cause race conditions
* 2. incrementSafe() - synchronized, prevents race conditions
*
* The difference between these methods demonstrates why synchronization matters!
*/
static class Counter {
private int count = 0; // Shared data that multiple threads will access
/**
* UNSAFE INCREMENT - Can cause race conditions!
*
* This method is NOT synchronized, so multiple threads can access it simultaneously.
* The operation "count++" is actually three steps:
* 1. Read count
* 2. Add 1
* 3. Write back
*
* If two threads do this at the same time, they might both read the same value,
* both add 1, and both write back the same result - losing one increment!
*/
public void incrementUnsafe() {
count++; // This is NOT thread-safe!
}
/**
* SAFE INCREMENT - Prevents race conditions!
*
* This method IS synchronized, so only one thread can execute it at a time.
* The synchronized keyword ensures that the entire operation is atomic
* from the perspective of other threads.
*
* This guarantees that each increment is properly counted.
*/
public synchronized void incrementSafe() {
count++; // This IS thread-safe!
}
/**
* Get the current count value
* Note: This method is not synchronized, but since we're only reading
* and the int type is atomic in Java, it's generally safe for this simple case.
*/
public int getCount() {
return count;
}
}
}