What is a thread and why is it important in software development?
A thread is the smallest sequence of instructions that a computer can execute, it's part of a process that can have multiple threads. Every Java program runs in a thread even if you don't create it, the main thread.
Understanding threads is important because we can make full use of the computer resources and write software with good performance, that has lower response time.
But although threads have all these advantages they also bring some complexity when dealing with multithreading programs.
This concept of multithreading was confusing to me, I didn't understand why my program had multiple threads if I hadn't created them?! But then I learned that some frameworks kindly create them for you, if you are from the Spring world it means that every request your application receives runs in a different thread, which means we must create thread-safe programs.
And what does it mean thread-safe?
I've read many concepts, in simple words it means that your program must behave predictably when accessed from multiple threads.
Let's say you have this simple class that has a method to increment a counter:
public class NoSynchronizedCounter {
public int counter = 0;
public void incrementCounter() {
counter++;
}
public Integer getCounter() {
return this.counter;
}
}
The expected behavior here is that every time the incrementCounter
is called the counter is incremented by one, in a single-threaded program that will be true, but if this code is called from multiple threads, then we can not guarantee that anymore. Let's execute the code:
NoSynchronizedCounter simpleCounter = new NoSynchronizedCounter();
Runnable runnable = () -> {
for (int i=0; i<10_000; i++) {
simpleCounter.incrementCounter();
}
};
Thread myThread1 = new Thread(runnable);
Thread myThread2 = new Thread(runnable);
myThread1.start();
myThread2.start();
myThread1.join();
myThread2.join();
System.out.println("Final counter: " + simpleCounter.getCounter());
We are calling our incrementCounter
method 10000 times, and running it in two different threads, we expect that the final counter would be 20000, but when I run this code I get a different result 14959
, but why does this happen?
In the NoSynchronizedCounter
class, the counter variable is shared between threads, and we can not guarantee that the threads will access the method in order. Two threads can access the incrementCounter
at the same time, if two threads read the same value for example 9, in the end, both threads will increment it to 10 not 11 as would be expected. We lost one increment and the final result will be wrong.
We need to make this class thread-safe, and we have different ways of doing that. One possible solution would be to change the counter type to AtomicInteger which guarantees that all the operations on counter variable will be atomic. Let's change it:
public class SynchronizedCounter {
public AtomicInteger counter = new AtomicInteger(0);
public void incrementCounter() {
counter.incrementAndGet();
}
public Integer getCounter() {
return counter.get();
}
}
Now let's test it:
SynchronizedCounter simpleCounter = new SynchronizedCounter();
Runnable runnable = () -> {
for (int i=0; i<10_000; i++) {
simpleCounter.incrementCounter();
}
};
Thread myThread = new Thread(runnable);
Thread myThread2 = new Thread(runnable);
myThread.start();
myThread2.start();
myThread.join();
myThread2.join();
System.out.println("Final counter: " + simpleCounter.getCounter());
The final result here will always be 20000, the AtomicInteger class is part of the Java concurrent package and it works well for this case as we have only one shared state between threads, things get more complicated when there are multiple shared states, but that's a topic for another moment.
I hope you find this helpful, thanks for reading!
References:
Java Concurrency in Practice - Brian Goetz
Effective Java - Joshua Bloch
Top comments (0)