線程安全問題?安全
什麼是線程安全問題?簡單的說,當多個線程在共享同一個變量,作讀寫的時候,會因爲其餘線程的干擾,致使數據偏差,就會出現線程安全問題。多線程
好比說,多個窗口同時賣票這個案例:併發
1 public class ThreadTrain2 implements Runnable { 2 private int tickets = 50; 3 @Override 4 public void run() { 5 while(tickets > 0){ 6 if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "賣了第" + (50 - tickets + 1) + "張票"); 7 tickets--; 8 } 9 } 10 } 11 } 12 public static void main(String[] args) { 13 ThreadTrain2 tt = new ThreadTrain2(); 14 Thread th1 = new Thread(tt, "1號窗口"); 15 Thread th2 = new Thread(tt, "2號窗口"); 16 th1.start(); 17 th2.start(); 18 } 19 }
模擬兩個窗口共同賣50張票,什麼都不考慮,按照上面的寫法,運行的結果有時候並非咱們想要的,會徹底亂了套。ide
使用多線程同步(synchronized)或者加鎖lock函數
什麼是多線程同步?就是當多個線程共享同一個資源時,不會受到其餘線程的干擾。this
爲何這兩種方法能夠解決線程的安全問題?spa
當把可能發生衝突的代碼包裹在synchronized或者lock裏面後,同一時刻只會有一個線程執行該段代碼,其餘線程必須等該線程執行完畢釋放鎖之後,才能去搶鎖,得到鎖之後,才擁有執行權,這樣就解決的數據的衝突,實現了線程的安全。線程
賣票的案例同步後爲:對象
1 public class ThreadTrain2 implements Runnable { 2 private int tickets = 50; 3 private static Object obj = new Object();//鎖的對象,能夠是任意的對象 4 @Override 5 public void run() { 6 while(tickets > 0){ 7 synchronized (obj) {// 同步代碼塊 8 if (tickets > 0) { 9 System.out.println(Thread.currentThread().getName() + "賣了第" + (50 - tickets + 1) + "張票"); 10 tickets--; 11 } 12 } 13 } 14 } 15 public static void main(String[] args) { 16 ThreadTrain2 tt = new ThreadTrain2(); 17 Thread th1 = new Thread(tt, "1號窗口"); 18 Thread th2 = new Thread(tt, "2號窗口"); 19 th1.start(); 20 th2.start(); 21 } 22 } 23
上面是同步代碼塊的加鎖方式,能夠解決線程安全問題。同時,還有一種同步函數的方式,就是在方法上直接加synchronized,能夠實現一樣的效果,那麼如今有一個問題,在方法上加synchronized修飾,鎖的對象是什麼呢???this。。下面來驗證一下爲何是this:blog
點擊+號查看代碼,代碼中的執行結果是絕對正確的,咱們是採用一個線程使用同步代碼塊,另外一個線程使用同步函數的方式,看是否會發生數據錯誤,做爲對比,下面的代碼中同步代碼塊咱們不使用this,而是使用obj這個對象:
顯然,這段代碼最後會出現數據衝突的狀況,由於兩個線程拿到的不是同一把鎖,也證實了同步函數鎖的是this。
明白了同步函數的鎖是this,那麼加上static之後,鎖的對象會不會發生改變,仍是依然是this???
先鎖this,驗證是不是this:
出現了數據錯誤,這裏咱們不作猜想,只作驗證,靜態的同步函數鎖的是當前類的字節碼文件,代碼驗證:
同步中嵌套同步,鎖沒有來得及釋放,一直等待,就致使死鎖。
下面這段代碼,多運行幾回就會出現死鎖,思路是開啓兩個線程,讓這兩個線程執行的代碼獲取的鎖的順序不一樣,第一個線程須要先得到obj對象鎖,而後再得到this鎖,才能夠執行代碼,而後釋放兩把鎖。線程2須要先得到this鎖,再獲取obj對象鎖纔可執行代碼,而後釋放兩把鎖。可是,當線程1得到了obj鎖以後,線程2得到了this鎖,這時候線程1須要得到this鎖纔可執行,可是線程2也沒法獲取到obj對象鎖執行代碼並釋放,因此兩個線程都拿着一把鎖不釋放,這就產生了死鎖。
原子性就是在執行一個或者多個操做的過程當中,要麼所有執行完不被任何因素打斷,要麼不執行。好比銀行轉帳,A帳戶減去100元,B帳戶必須增長100元,對這兩個帳戶的操做必須保證原子性,纔不會出現問題。還有好比:i=i+1的操做,須要先取出i,而後對i進行+1操做,而後再給i賦值,這個式子就不是原子性的,須要同步來實現數據的安全。
原子性就是爲了保證數據一致,線程安全。
當多個線程訪問同一個變量時,一個線程修改了變量的值,其餘的線程能當即看到,這就是可見性。
這裏講一下Java內存模型?簡稱JMM,決定了一個線程與另外一個線程是否可見,包括主內存(存放共享的全局變量)和私有本地內存(存放本地線程私有變量)
本地私有內存存放的是共享變量的副本,線程操做共享變量,首先操做的是本身本地內存的副本,當同一時刻只有一個線程操做共享變量時,該線程操做完畢本地內存,而後會刷新到主內存,而後主內存會通知另外一個線程,進而更新;可是若是同一時刻有多個線程操做共享變量,會來不及更新主內存進而通知其餘線程更新變量,就會出現衝突問題。
就是程序的執行順序會按照代碼前後順序進行執行,通常狀況下,處理器因爲要提升執行效率,對代碼進行重排序,運行的順序可能和代碼前後順序不一樣,可是結果同樣。單線程下不會出現問題,多線程就會出現問題了。
保證可見性,可是不保證原子性。
下面這個案例10個線程共享同一個count,進行+1操做:
public class VolatileTest extends Thread{ private volatile static int count = 0; @Override public void run() { for (int i = 0; i < 100; i++) { count++; } System.out.println(Thread.currentThread().getName()+":"+count); } public static void main(String[] args) { VolatileTest[] list = new VolatileTest[10]; for (int i = 0; i < list.length; i++) { list[i] = new VolatileTest(); } for (int i = 0; i < list.length; i++) { list[i].start(); } } }
多運行幾回,就會出現最後結果有不到1000的狀況,也就證實了volatile不會保證原子性。
保證原子性,jdk1.5以後,併發包提供了不少原子類,例如AtomicInteger :
public class VolatileTest2 extends Thread{ private static AtomicInteger count = new AtomicInteger(); @Override public void run() { for (int i = 0; i < 100; i++) { count.incrementAndGet(); } System.out.println(Thread.currentThread().getName()+":"+count.get()); } public static void main(String[] args) { VolatileTest2[] list = new VolatileTest2[10]; for (int i = 0; i < list.length; i++) { list[i] = new VolatileTest2(); } for (int i = 0; i < list.length; i++) { list[i].start(); } } }
AtomicInteger解決了同步, 最後的結果最大的確定是1000