Thread Safety and Thread Insecurity

Posted by immunity on Thu, 13 Jun 2019 00:20:48 +0200

What is thread safety?

The more official explanations are as follows:

When multiple threads access a class, no matter how the runtime environment is scheduled or how these threads are executed alternately, and there is no need for any additional synchronization or collaboration in the main code, this class can show the correct behavior, so it is called thread-safe.

The above explanation may seem confusing, but it doesn't matter. Next, use the code to explain what thread safety is.

Let's start with thread insecurity

package thread;

import java.util.concurrent.CountDownLatch;

/**
 * 
 * @author wiggin
 * Thread insecure class
 *
 */
public class Unsafe {

	private static int num;
	
	//CountDownLatch is used to ensure that all 10 threads are executed.
	public static CountDownLatch countDownLatch = new CountDownLatch(10);
	
	public static void increate () {
		num++;
	}
	
	public static void main(String[] args) throws InterruptedException {
		for (int i = 0; i < 10; i++) {
			//lambda expression of Java 8
			new Thread(() -> {
				int j = 1000;
				while (j > 0) {
					increate();
					j--;
				}
				//After each thread is executed, call countDown and subtract it by one
				countDownLatch.countDown();
			}).start();
		}
		
		
		while (true) { 
			if (countDownLatch.getCount() == 0) {
				System.out.println(num);
				break;
			}
			
		}
		
	}
}

The program above starts ten threads, each threads accumulating the number num 1000 times. The num is 10000 after accumulating under ideal condition, but you can try running many times, and you will find that the results are different each time.

The reason for this is that the ++ operation is not an atomic operation. Although it seems to have only one line and is very compact, it contains three operations:

Reading the value of num

(2) Value + 1

(3) The result of calculation is assigned to num.

From the bytecode sequence generated by the code, you can see the three steps above.

public static void increate();

   Code:

0: getstatic # 2 Gets the static domain of the specified class and puts it on the top of the stack

3: iconst_1) Push int-1 to the top of the stack

4: iadd) Add two int integers on the top of the stack and put the results on the top of the stack

5: putstatic # 2 assigns values to static fields of specified classes

8: return * Return * Return *


When multi-threaded operations occur, multiple threads may read the same num value at the same time, and then accumulate, which will lead to inaccurate results. The general process is as follows:



How can you make it thread-safe?

One might think of using volatile to modify variables for num. Unfortunately, volatile values guarantee visibility, but do not guarantee atomicity. That is to say, after the current thread modifies the num value, it can be immediately visible to other threads, but there is still no guarantee that num++ is an atomic operation. That's the same thing.

The correct methods are:

Use synchronized keywords

package thread;

import java.util.concurrent.CountDownLatch;

/**
 * 
 * @author wiggin
 * Thread Safety Class
 *
 */
public class Safe {

	private static int num;
	
	//CountDownLatch is used to ensure that all 10 threads are executed.
	public static CountDownLatch countDownLatch = new CountDownLatch(10);
	
	public static void increate () {
		synchronized(countDownLatch){
			num ++;
		}
		
	}
	
	public static void main(String[] args) throws InterruptedException {
		for (int i = 0; i < 10; i++) {
			//lambda expression of Java 8
			new Thread(() -> {
				int j = 1000;
				while (j > 0) {
					increate();
					j--;
				}
				//After each thread is executed, call countDown and subtract it by one
				countDownLatch.countDown();
			}).start();
		}
		
		
		while (true) { 
			if (countDownLatch.getCount() == 0) {
				System.out.println(num);
				break;
			}
			
		}
		
	}
}

(2) Modify num to AtomicInteger variable

package thread;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 
 * @author wiggin
 * Thread Safety Class
 *
 */
public class Safe {

	//Change the variable to Atomic Integer
	private static AtomicInteger num = new AtomicInteger(0);
	
	//CountDownLatch is used to ensure that all 10 threads are executed.
	public static CountDownLatch countDownLatch = new CountDownLatch(10);
	
	public static void increate () {
			num.incrementAndGet();
	}
	
	public static void main(String[] args) throws InterruptedException {
		for (int i = 0; i < 10; i++) {
			//lambda expression of Java 8
			new Thread(() -> {
				int j = 1000;
				while (j > 0) {
					increate();
					j--;
				}
				//After each thread is executed, call countDown and subtract it by one
				countDownLatch.countDown();
			}).start();
		}
		
		
		while (true) { 
			if (countDownLatch.getCount() == 0) {
				System.out.println(num);
				break;
			}
			
		}
		
	}
}

Use locks

package thread;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 
 * @author wiggin
 * Thread Safety Class
 *
 */
public class Safe {

	private static int num;
	
	//Use locks
	private static ReentrantLock reentrantLock = new ReentrantLock();
	
	//CountDownLatch is used to ensure that all 10 threads are executed.
	public static CountDownLatch countDownLatch = new CountDownLatch(10);
	
	public static  void increate () {
		//Using reentrantLock, locks must be explicitly released
		try {
			reentrantLock.lock();
			num ++;
		} finally {
			reentrantLock.unlock();
		}
		
		
	}
	
	public static void main(String[] args) throws InterruptedException {
		for (int i = 0; i < 10; i++) {
			//lambda expression of Java 8
			new Thread(() -> {
				int j = 1000;
				while (j > 0) {
					increate();
					j--;
				}
				//After each thread is executed, call countDown and subtract it by one
				countDownLatch.countDown();
			}).start();
		}
		
		
		while (true) { 
			if (countDownLatch.getCount() == 0) {
				System.out.println(num);
				break;
			}
			
		}
		
	}
}






Topics: Java Lambda