本篇主要講解 線程安全問題,演示什麼狀況下會出現線程安全問題,以及介紹了 Java內存模型 、volatile關鍵字 、CAS 等 ,最後感謝吳恆同窗的投稿! 一塊兒來了解吧!!java
運行以下程序:編程
/** * @program: * @description: 多線程操做的對象 * @author: * @create: **/ public class MyCount { private int myCount = 0 ; public int getMyCount() { return myCount; } public void setMyCount(int myCount) { this.myCount = myCount; } @Override public String toString() { return "MyCount{" + "myCount=" + myCount + '}'; } }
建立線程安全
public class CountThread1 extends Thread{ private MyCount myCount ; private static Object synch = new Object(); public CountThread1( MyCount myCount) { this.myCount = myCount; } @Override public void run() { //myCount 加到100 while (true) { if(myCount.getMyCount()<100) { myCount.setMyCount(myCount.getMyCount() + 1); System.out.println(Thread.currentThread().getName() + " set myCount值:" + myCount.getMyCount()); }else{ break; } try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
}多線程
運行下列線程ide
public static void main(String[] args) { MyCount myCount = new MyCount(); CountThread1 c1 = new CountThread1(myCount); CountThread1 c2 = new CountThread1(myCount); CountThread1 c3 = new CountThread1(myCount); c1.setName("c1"); c2.setName("c2"); c3.setName("c3"); c1.start(); c2.start(); c3.start(); }
測試結果:性能
以上是多線程同時對同一變量進行操做時,發生的非線程安全問題。換句話說只用共享資源的讀寫訪問才須要同步化,若是不是共享資源,那麼根本沒有同步的必要。測試
某些JVM運行中,有兩塊主要的內存,一個是主內存,另一個是每一個線程都具備的工做內存。this
1. 從主內存中copy要操做的數據到本身的工做內存中去。
2. 線程主體從本身的工做內存中讀取數據進行操做。
3. 操做完成後,在同步到主內存中去。線程
結合線程運行的流程,上述多線程可能會出現如下執行流程:code
1. c3線程得到cpu資源,執行+1操做,在c3想同步count值1到主內存中去時。
2. c2線程獲得了cpu的資源,也一樣執行+1操做,但沒+1前count的值是0,而不是1,c2執行完後,打印count=1的值,而且把數據同步到主內存中。
3. 此時c3又獲得了cpu的資源,於所執行剛纔沒有完成的同步操做,同時又打印count=1的值。
這就致使出現上圖結果的緣由。
解決上述問題最多見的方法就是在線程的run方法上添加synchronized關鍵字
while (true) { synchronized (synch) { if(myCount.getMyCount()<100) { myCount.setMyCount(myCount.getMyCount() + 1); System.out.println(Thread.currentThread().getName() + " set myCount值:" + myCount.getMyCount()); }else{ break; } try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
以下線程:
public class CountThread2 extends Thread{ private boolean isRunning = true; @Override public void run() { System.out.println("進入到run方法了"); while (isRunning) { } System.out.println("run方法結束"); } public void setNotRunning() { System.out.println("isRunning爲false"); isRunning = false; } } 執行以下方法 CountThread2 ct2 = new CountThread2(); ct2.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } ct2.setNotRunning();
結果以下:
明明isRunning被設置爲false,爲何線程尚未結束?
還要把這個圖拿過來
當主線程把isRunning設置爲false,並同步到主線程後,當 ct2 線程執行while(isRunning)時,isRunning的值是從工做內存中獲取的,也就是多線程同時操做同一屬性時,當主內存中變量被修改時,其它線程並無感知到該變量被修改。
代碼修改 //變量添加 volatile關鍵字 private volatile boolean isRunning = true;
執行結果以下:
發現當isRunning被修改成false時,ct2線程就結束了。這是爲何了?
當線程修改volatile變量後,會馬上同步到主內存中,同時迫使在使用該變量的其它線程去主內存中同步該變量到各自的工做內存中。但遺憾的是volatile不具備原子性
上圖是對線程內存模型進一步的描述,一線程在執行use操做時,忽然時去了cpu執行權限(cpu執行任何原子性操做時,是不可能出現中斷的),也就出現上述非線程安全的問題了。
多線程在訪問synchronized同步區域時,若是一線程獲取到同步鎖,其它線程就會被阻塞,就會造成線程安全的機制。
既然線程安全包含原子性和可見性 ,synchronized具備線程安全的功能,那麼synchronized具備原子性和可見性?
這種等比性思想對嗎?爲何synchronized具備原子性和可見性
方法一: private int i= 1; synchronized public void run() { System.out.println("i++:"+i); } 方法二: private volatile int i= 1; public void run() { System.out.println("i++:"+i); }
當多線程訪問方法一,多個線程依次訪問方法二,我想兩種類型都是線程安全,且結果一致的。那你對synchronized又有什麼新的想法了?synchronized = volatile+非當前線程阻塞
修改MyCount代碼
public class MyCount { private AtomicInteger myCount = new AtomicInteger(0); public int getMyCount() { return myCount.get(); } public int setMyCount() { return myCount.incrementAndGet(); } }
繼續執行以下操做
MyCount myCount = new MyCount(); CountThread1 c1 = new CountThread1(myCount); CountThread1 c2 = new CountThread1(myCount); CountThread1 c3 = new CountThread1(myCount); c1.setName("c1"); c2.setName("c2"); c3.setName("c3"); c1.start(); c2.start(); c3.start(
你會發現線程是安全的,這又是爲何了?這種線程安全的緣由和synchronized又有什麼聯繫和區別?
synchronized是一種悲觀鎖機制,有一線程獲取鎖後,其它線程就被阻塞,經過這種方法能夠到達線程全的效果。多線程競爭的狀況下會出現阻塞和喚醒的性能問題。
CAS compare and swap ,先比較而後交換
上述代碼 myCount.incrementAndGet();源碼以下 public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } getAndAddInt(this, valueOffset, 1) + 1;源碼以下 public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; } public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
現描述:getAndAddInt()方法中個參數的意思
①: var5 = this.getIntVolatile(var1, var2);
var1 : myCount值在內存中的首地址
var2:myCount值在內存中的偏移量
var5 = this.getIntVolatile(var1, var2); 也就是獲取當前myCount的值
②:!this.compareAndSwapInt(var1, var2, var5, var5 + var4)
var1,和 var2 獲取內存中myCount最新的值,
var4: var4就是1,
var5是 this.getIntVolatile(var1, var2)獲取的舊值
var5 + var4是myCount將要獲得的值
判斷這兩個值是否相等,若是不相等,就代表myCount的值被其它線程操做,myCount值不是最新的,須要重新獲取,也就是重新執行 ① 和②,直到值新舊值相等時,代表沒有其它線程在操做此變量,而後
就把var5 + var4 賦值給 var5,從而達到線程安全。
上述的流程,就是CAS想要表達的思想,多線程訪問同一臨界區域時,都認爲沒有上鎖,經過先比較來確認是否發生衝突,直到沒有衝突時執行交換操做。這也同時解決了synchronized 多線程競爭的狀況下會出現阻塞和喚醒的性能問題。
備註:以上線程內存模型相關圖片來自於高洪巖《Java 多線程編程核心技術》
本篇主要介紹了 線程安全問題,Java內存模型,volatile關鍵字 synchronized關鍵字 CAS 等
最後 感謝吳恆同窗的投稿 !!!
我的博客地址: https://www.askajohnny.com 歡迎訪問!
本文由博客一文多發平臺 OpenWrite 發佈!