1、可見性(Visibility)
可見性是指,當一個線程修改了某一個全局共享變量的數值,其他線程是否能夠知道這個修改。
顯然,在串行程序來說可見性的問題是不存在的。因為你在任何一個地方操作修改了某個變量,那么在后續的程序里面,讀取這個變量的數值,一定是修改后的數值。
但是,這個問題在並行程序里面就不見得了。在並行程序里面,如果一個線程修改了某一個全局變量,那么其他線程未必可以馬上知道這個變動。下面的圖1展示了可見性問題的一種。如果在CPU1和CPU2上各運行一個線程,他們共享變量t,由於編譯器優化或者應該優化的緣故,在CPU1上的線程將變量t進行了優化,將其緩存在Cache中或者寄存器里面。這種情況下,如果在CPU2上的那個線程修改了線程t 的實際數值,那么CPU1上的線程可能並無法意識到這個改動,依然會讀取cache中或者寄存器里面的數據。因此,就產生了可見性問題。外在表現就是,變量t 的數值被CPU2上的線程修改,但是CPU1上的線程依然會讀到一個舊的數據。
2、原子性(Atomicity)
原子性,指的是一個操作是不可中斷的。即使是在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程打斷。
3、Java內存模型的抽象結構( JMM )
在Java中,所有實例域、靜態域和數組元素都存儲在堆內存中,堆內存在線程之間共享(本章用“共享變量”這個術語代指實例域,靜態域和數組元素)。局部變量(Local Variables),方法定義參數(Java語言規范稱之為Formal Method Parameters)和異常處理器參數(ExceptionHandler Parameters)不會在線程之間共享,它們不會有內存可見性問題,也不受內存模型的影響。
Java線程之間的通信由Java內存模型(本文簡稱為JMM)控制,JMM決定一個線程對共享變量的寫入何時對另一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關系:線程之間的共享變量存儲在主內存(Main Memory)中,每個線程都有一個私有的本地內存(Local Memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存、寫緩沖區、寄存器以及其他的硬件和編譯器優化。Java內存模型的抽象示意如圖所示。
圖3-1 Java內存模型的抽象結構示意圖
從圖3-1來看,如果線程A與線程B之間要通信的話,必須要經歷下面2個步驟。
1)線程A把本地內存A中更新過的共享變量刷新到主內存中去。
2)線程B到主內存中去讀取線程A之前已更新過的共享變量。
下面通過示意圖(見圖3-2)來說明這兩個步驟。
圖3-2 線程之間的通信圖
如圖3-2所示,本地內存A和本地內存B由主內存中共享變量x的副本。假設初始時,這3個內存中的x值都為0。線程A在執行時,把更新后的x值(假設值為1)臨時存放在自己的本地內存A中。當線程A和線程B需要通信時,線程A首先會把自己本地內存中修改后的x值刷新到主內存中,此時主內存中的x值變為了1。隨后,線程B到主內存中去讀取線程A更新后的x值,此時線程B的本地內存的x值也變為了1。
從整體來看,這兩個步驟實質上是線程A在向線程B發送消息,而且這個通信過程必須要經過主內存。JMM通過控制主內存與每個線程的本地內存之間的交互,來為Java程序員提供內存可見性保證。
4、volatile
4.1使用volatile以后,做了如下事情
- 每次修改volatile變量都會同步到主存中。
- 每次讀取volatile變量的值都強制從主存讀取最新的值(強制JVM不可優化volatile變量,如JVM優化后變量讀取會使用cpu緩存而不從主存中讀取)
4.2 volatile解決的是多線程間共享變量的可見性問題,而保證不了多線程間共享變量原子性問題。對於多線程的i++,++i,依然還是會存在多線程問題,volatile是無法解決的.如下:使用一個線程i++,另一個i--,最終得到的結果不為0。
4.2.1 多線程下的i++問題
一個線程對count進行times次的加操作,一個線程對count進行times次的減操作。count最后的結果,不為0.
public class VolatileTest { private static volatile int count = 0; private static final int times = 10000; public static void main(String[] args) { long curTime = System.nanoTime(); Thread decThread = new DecThread(); decThread.start(); System.out.println("Start thread: " + Thread.currentThread() + " i++"); for (int i = 0; i < times; i++) { count++; } System.out.println("End thread: " + Thread.currentThread() + " i--"); // 等待decThread結束 while (decThread.isAlive()) ; long duration = System.nanoTime() - curTime; System.out.println("Result: " + count); System.out.format("Duration: %.2fs\n", duration / 1.0e9); } private static class DecThread extends Thread { @Override public void run() { System.out.println("Start thread: " + Thread.currentThread() + " i--"); for (int i = 0; i < times; i++) { count--; } System.out .println("End thread: " + Thread.currentThread() + " i--"); } } }
4.2.2 程序的運行結果
Start thread: Thread[Thread-0,5,main] i-- Start thread: Thread[main,5,main] i++ End thread: Thread[main,5,main] i++ End thread: Thread[Thread-0,5,main] i-- Result: -6240 Duration: 0.00s
4.2.3 i++和++i並非原子操作
原因是i++和++i並非原子操作,我們若查看字節碼,會發現
void f1() { i++; }
的字節碼如下
void f1(); Code: 0: aload_0 1: dup 2: getfield #2; //Field i:I 5: iconst_1 6: iadd 7: putfield #2; //Field i:I 10: return
可見i++執行了多部操作,從變量i中讀取讀取i的值->值+1 ->將+1后的值寫回i中,這樣在多線程的時候執行情況就類似如下了
Thread1 Thread2 r1 = i; r3 = i; r2 = r1 + 1; r4 = r3 + 1; i = r2; i = r4;
這樣會造成的問題就是 r1, r3讀到的值都是 0,最后兩個線程都將 1 寫入 i, 最后 i等於 1,但是卻進行了兩次自增操作。
可知加了volatile和沒加volatile都無法解決非原子操作的線程同步問題。
5、使用循環CAS,實現i++原子操作
5.1 關於Java並發包的介紹
Java提供了java.util.concurrent.atomic包來提供線程安全的基本類型包裝類。這些包裝類都是是用CAS來實現,i++的原子性操作。以AtomicInteger為例子,講一下 public final int getAndIncrement(){} 方法的實現。
public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } }
5.2 使用循環CAS,來實現i++的原子性操作
public class AtomicIntegerTest { private static AtomicInteger count = new AtomicInteger(0); private static final int times = 10000; AtomicInteger atomicInteger; public static void main(String[] args) { long curTime = System.nanoTime(); Thread decThread = new DecThread(); decThread.start(); System.out.println("Start thread: " + Thread.currentThread() + " i++"); for (int i = 0; i < times; i++) { // 進行自加的操作 count.getAndIncrement(); } System.out.println("End thread: " + Thread.currentThread() + " i++"); // 等待decThread結束 while (decThread.isAlive()) ; long duration = System.nanoTime() - curTime; System.out.println("Result: " + count); System.out.format("Duration: %.2fs\n", duration / 1.0e9); } private static class DecThread extends Thread { @Override public void run() { System.out.println("Start thread: " + Thread.currentThread() + " i--"); for (int i = 0; i < times; i++) { // 進行自減的操作 count.getAndDecrement(); } System.out .println("End thread: " + Thread.currentThread() + " i--"); } } }
5.3 程序的運行結果
Start thread: Thread[main,5,main] i++ Start thread: Thread[Thread-0,5,main] i-- End thread: Thread[Thread-0,5,main] i-- End thread: Thread[main,5,main] i++ Result: 0 Duration: 0.00s
6、使用鎖機制,實現i++原子操作
6.1 使用鎖機制,實現i++原子操作
package com.baowei.yuanzi; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; public class LockTest { private static int count = 0; private static final int times = 10000; // 使用Lock實現,多線程的數據同步 public static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { long curTime = System.nanoTime(); Thread decThread = new DecThread(); decThread.start(); System.out.println("Start thread: " + Thread.currentThread() + " i++"); for (int i = 0; i < times; i++) { // 進行自加的操作 try { lock.lock(); count++; } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } System.out.println("End thread: " + Thread.currentThread() + " i++"); // 等待decThread結束 while (decThread.isAlive()) ; long duration = System.nanoTime() - curTime; System.out.println("Result: " + count); System.out.format("Duration: %.2fs\n", duration / 1.0e9); } private static class DecThread extends Thread { @Override public void run() { System.out.println("Start thread: " + Thread.currentThread() + " i--"); for (int i = 0; i < times; i++) { // 進行自減的操作 try { lock.lock(); count--; } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } System.out .println("End thread: " + Thread.currentThread() + " i--"); } } }
6.2 程序的運行結果
Start thread: Thread[Thread-0,5,main] i-- Start thread: Thread[main,5,main] i++ End thread: Thread[main,5,main] i++ End thread: Thread[Thread-0,5,main] i-- Result: 0 Duration: 0.04s
7、使用synchronized,實現i++原子操作
7.1 使用synchronized,實現i++原子操作
package com.baowei.yuanzi; public class SynchronizedTest { private static int count = 0; private static final int times = 1000000; public static void main(String[] args) { long curTime = System.nanoTime(); Thread decThread = new DecThread(); decThread.start(); System.out.println("Start thread: " + Thread.currentThread() + " i++"); for (int i = 0; i < times; i++) { // 進行自加的操作 synchronized (SynchronizedTest.class) { count++; } } System.out.println("End thread: " + Thread.currentThread() + " i++"); // 等待decThread結束 while (decThread.isAlive()) ; long duration = System.nanoTime() - curTime; System.out.println("Result: " + count); System.out.format("Duration: %.2fs\n", duration / 1.0e9); } private static class DecThread extends Thread { @Override public void run() { System.out.println("Start thread: " + Thread.currentThread() + " i--"); for (int i = 0; i < times; i++) { // 進行自減的操作 synchronized (SynchronizedTest.class) { count--; } } System.out .println("End thread: " + Thread.currentThread() + " i--"); } } }
7.2 程序的運行結果
Start thread: Thread[main,5,main] i++ Start thread: Thread[Thread-0,5,main] i-- End thread: Thread[Thread-0,5,main] i-- End thread: Thread[main,5,main] i++ Result: 0 Duration: 0.03s
8、參考的資料
8.1 Java 高並發程序設計(21頁)
8.2 http://www.cnblogs.com/zemliu/p/3298685.html
8.3 Java 並發編程的藝術 ( The Art of Java Concurrency Programming ) (22-23 頁)
————————————————
版權聲明:本文為CSDN博主「Bwz_Learning」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zbw18297786698/article/details/53420780