What exactly are we talking about when we say thread safety?

Java Advanced (2) When we say thread safety, what are we talking about?

Posted on 2016-06-13 | Updated on 2017-02-15 | Category in java | Comments 0 | Views 21633 | Word Count 4,027

When it comes to thread safety, your first reaction may be to ensure that the interface's operations on shared variables are specific and atomic. In fact, in multithreaded programming we need to pay attention to visibility, sequentiality and atomicity at the same time. This article will start from these three questions, combined with examples to explain how volatile guarantees visibility and certain procedures to ensure sequentiality, and at the same time, gives examples of how synchronized guarantees both visibility and atomicity, and finally compares the applicable scenarios of volatile and synchronized.

Original article, please be sure to place the following paragraph at the beginning of the article (retain the hyperlink) for reprinting.
This article is forwarded from the technology world , the original link is  http://www.jasongj.com/java/thread_safe/

Three core concepts in multithreaded programming

atomicity

This is similar to the atomic concept of database transactions, that is, an operation (which may contain multiple sub-operations) is either all executed (effective) or none of them are executed (not effective).

A very classic example of atomicity is the bank transfer problem: for example, A and B transfer 100,000 yuan to C at the same time. If the transfer operation is not atomic, when A transfers money to C, it reads the balance of C as 200,000, and then adds 100,000 of the transfer, and calculates that there should be 300,000 at this time, but it is still in the future and writes 300,000 Back to C's account, when B's transfer request came, B found that C's balance was 200,000, then added 100,000 to it and wrote it back. Then A's transfer operation continues - 300,000 is written back to C's balance. The final balance of C in this case is 300,000 instead of the expected 400,000.

visibility

Visibility means that when multiple threads access a shared variable concurrently, the modification of a shared variable by one thread can be immediately seen by other threads. The visibility issue is something that many people overlook or misunderstand.

The efficiency of the CPU reading data from the main memory is relatively inefficient. In mainstream computers, there are several levels of cache. When each thread reads a shared variable, it loads the variable into its corresponding CPU cache. After modifying the variable, the CPU updates the cache immediately, but does not necessarily immediately write it back to main memory (actually Unpredictable time to write back to main memory). At this time, when other threads (especially threads not executing on the same CPU) access the variable, the old data is read from the main memory, not the updated data of the first thread.

This is a mechanism at the operating system or hardware level, so many application developers often ignore it.

sequential

Sequential means that the order in which the program is executed is executed in the order in which the code is executed.

Take the following code as an example

1
2
3
4
boolean started = false ; // statement 1
long counter = 0L ; // statement 2
counter = 1 ; // statement 3
started = true ; // statement 4

In terms of code order, the above four statements should be executed in sequence, but in fact, when the JVM actually executes this code, it does not guarantee that they must be executed in exactly this order.

In order to improve the overall execution efficiency of the program, the processor may optimize the code. One of the optimization methods is to adjust the code order to execute the code in a more efficient order.

Speaking of this, someone has to be anxious - what, the CPU does not execute the code in the order of my code, so how can we guarantee the effect we want? In fact, you can rest assured that although the CPU does not guarantee that the code is executed in complete order, it will ensure that the final execution result of the program is the same as the result of the code sequence execution.

How does Java solve the problem of multithreading concurrency

How Java guarantees atomicity

locks and synchronization

Commonly used tools to ensure atomicity of Java operations are locks and synchronized methods (or synchronized code blocks). Using locks can ensure that only one thread can get the lock at the same time, which also ensures that only one thread can execute the code between applying for the lock and releasing the lock at the same time.

1
2
3
4
5
6
7
8
9
publicvoidtestLock(){
  lock.lock();
  try{
    int j = i;
    i = j + 1;
  } finally {
    lock.unlock();
  }
}

Similar to locks are synchronized methods or synchronized blocks of code. When using a non-static synchronization method, the current instance is locked; when using a static synchronization method, the Class object of the class is locked; when using a static code block, synchronized the object in the brackets after the keyword is locked. Below is an example of a synchronized code block

1
2
3
4
5
6
publicvoidtestLock(){
  synchronized (anyObject){
    int j = i;
    i = j + 1;
  }
}

Whether using locks or synchronized, the essence is the same. The exclusiveness of resources is achieved through locks, so that the actual target code segment will only be executed by one thread at the same time, thereby ensuring the atomicity of the target code segment. This is an approach at the expense of performance.

CAS(compare and swap

Fundamental type variable auto-increment (i++ ) is an operation that is often mistaken for an atomic operation by novices when it is not. A corresponding atomic operation class is provided in Java to implement this operation and ensure atomicity. Its essence is to use the CAS instruction at the CPU level. Since it is a CPU-level instruction, its overhead is less than that of a lock that requires the participation of the operating system. AtomicInteger is used as follows.

1
2
3
4
5
6
7
8
AtomicInteger atomicInteger = new AtomicInteger();
for(int b = 0; b < numThreads; b++) {
  new Thread(() -> {
    for(int a = 0; a < iteration; a++) {
      atomicInteger.incrementAndGet();
    }
  }).start();
}

How Java guarantees visibility

Java provides volatilekeywords to guarantee visibility. When a variable is modified with volatile, it will ensure that the modification of the variable will be updated to the memory immediately, and the cache of the variable in other caches will be invalidated, so other threads must read the value from the Read from main memory to get the latest value.

How does Java guarantee ordering?

As mentioned above, when the compiler and the processor reorder the instructions, they will ensure that the reordered execution result is consistent with the code sequence execution result, so the reordering process will not affect the execution of the single-threaded program, but it may affect multiple Correctness of concurrent execution of threaded programs.

In Java , order can be guaranteed by volatilecertain programs, and can also be guaranteed by synchronized and locks.

The principle of synchronization and lock guaranteeing sequence is the same as guaranteeing atomicity, which is achieved by ensuring that only one thread executes the target code segment at the same time.

In addition to guaranteeing the ordering of target code segment execution at the application level, the JVM also implicitly guarantees ordering through what is known as the happens-before principle. As long as the execution order of the two operations can be deduced through happens-before, the JVM will guarantee its order. Otherwise, the JVM does not make any guarantee of its order, and can reorder them as necessary to achieve high efficiency.

happens-before原则(先行发生原则)

o     传递规则:如果操作1在操作2前面,而操作2在操作3前面,则操作1肯定会在操作3前发生。该规则说明了happens-before原则具有传递性

o     锁定规则:一个unlock操作肯定会在后面对同一个锁的lock操作前发生。这个很好理解,锁只有被释放了才会被再次获取

o     volatile变量规则:对一个被volatile修饰的写操作先发生于后面对该变量的读操作

o     程序次序规则:一个线程内,按照代码顺序执行

o     线程启动规则:Thread对象的start()方法先发生于此线程的其它动作

o     线程终结原则:线程的终止检测后发生于线程中其它的所有操作

o     线程中断规则: 对线程interrupt()方法的调用先发生于对该中断异常的获取

o     对象终结规则:一个对象构造先于它的finalize发生

volatile适用场景

volatile适用于不需要保证原子性,但却需要保证可见性的场景。一种典型的使用场景是用它修饰用于停止线程的状态标记。如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
boolean isRunning = false;
publicvoidstart(){
  new Thread( () -> {
    while(isRunning) {
      someOperation();
    }
  }).start();
}
publicvoidstop(){
  isRunning = false;
}

在这种实现方式下,即使其它线程通过调用stop()方法将isRunning设置为false,循环也不一定会立即结束。可以通过volatile关键字,保证while循环及时得到isRunning最新的状态从而及时停止循环,结束线程。

线程安全十万个为什么

问:平时项目中使用锁和synchronized比较多,而很少使用volatile,难道就没有保证可见性?
答:锁和synchronized即可以保证原子性,也可以保证可见性。都是通过保证同一时间只有一个线程执行目标代码段来实现的。


问:锁和synchronized为何能保证可见性?
答:根据JDK 7的Javadoc中对concurrent包的说明,一个线程的写结果保证对另外线程的读操作可见,只要该写操作可以由happen-before原则推断出在读操作之前发生。

The results of a write by one threadare guaranteed to be visible to a read by another thread onlyif the write operation happens-before the read operation. The synchronized andvolatile constructs, as well as the Thread.start() and Thread.join() methods,can form happens-before relationships.

问:既然锁和synchronized即可保证原子性也可保证可见性,为何还需要volatile?
答:synchronized和锁需要通过操作系统来仲裁谁获得锁,开销比较高,而volatile开销小很多。因此在只需要保证可见性的条件下,使用volatile的性能要比使用锁和synchronized高得多。

问:既然锁和synchronized可以保证原子性,为什么还需要AtomicInteger这种的类来保证原子操作?
答:锁和synchronized需要通过操作系统来仲裁谁获得锁,开销比较高,而AtomicInteger是通过CPU级的CAS操作来保证原子性,开销比较小。所以使用AtomicInteger的目的还是为了提高性能。

问:还有没有别的办法保证线程安全
答:有。尽可能避免引起非线程安全的条件——共享变量。如果能从设计上避免共享变量的使用,即可避免非线程安全的发生,也就无须通过锁或者synchronized以及volatile解决原子性、可见性和顺序性的问题。

Q: Synchronized can modify non-static methods, static methods, and code blocks. What is the difference
? Answer: When synchronized modifies a non-static synchronization method, the current instance is locked; when synchronized modifies a static synchronization method, the lock The Class object of the class lives; when synchronized modifies a static code block, synchronizedthe object in the parentheses after the keyword is locked.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324629038&siteId=291194637