首先要知道什么是線程安全?
當多個線程訪問某個類時,不管運行環(huán)境采用何種調度方式或者這些線程將如何交替執(zhí)行,并且在主調代碼中不需要任何額外的同步或協(xié)同,這個類都能表現(xiàn)出正確的行為,那么就稱這個類是線程安全的。
典型線程不安全的列子:
1 import java.util.*; 4 5 class Worker implements Runnable { 6 PRivate UnsafeCount unsafeCount; 7 8 public Worker(UnsafeCount unsafeCount) { 9 this.unsafeCount = unsafeCount;10 }11 12 @Override13 public void run() {14 // TODO Auto-generated method stub15 for (int i = 0; i < 1000; i++)16 unsafeCount.increase();17 }18 19 }20 21 public class UnsafeCount {22 private int count = 0;23 24 public void increase() {25 count++;26 }27 28 public int getCount() {29 return count;30 }31 32 public static void main(String[] args) throws InterruptedException {33 UnsafeCount uc = new UnsafeCount();34 35 //這里用了list簡陋的方式控制線程的結束,更好的實現(xiàn)是用閉鎖CountDownLatch或者柵欄CyclicBarrier36 List<Thread> list = new ArrayList<Thread>();//37 38 for (int i = 0; i < 10; i++) {39 Thread worker = new Thread(new Worker(uc));40 worker.start();41 list.add(worker);42 }43 44 //阻塞直到線程結束45 for (Thread t : list) {46 t.join();47 }48 49 System.out.println("total is: " + uc.getCount());50 51 }52 }
運行結果(每次結果都不一樣):total is: 7628
我們來仔細分析一下這個結果,開啟10個線程運行,每個線程都對count進行了1000次自增操作,期望的結果應該是1000*10=10000。很明顯運行結果與期望結果不一致。結論是這個類是線程不安全的。為什么會出現(xiàn)這種情況了?
原因是count++這個操作不是原子性,其實這個自增操作是個復合操作:讀-改-寫。 如果我們了解匯編語言的話,對應自增操作的匯編程序可能是:
movl count, %eax #將count的值讀入eax的寄存器中,inc %eax #寄存器eax里的值加1,即改寫count值movl %eax, %ebx #這里ebx寄存器存存放著count的內存地址,這里是值將改寫的count值寫入到內存中
那么這樣就存在一個問題,假如就存在2個線程A和B操作變量count,初始化時刻count為0. 在線程A未寫入改寫值之前,比如在A線程執(zhí)行步驟2的時刻, 線程B開始執(zhí)行,如下所示:
線程A讀入count值為0(步驟1) -》 改寫count值為1(步驟2) -》 將改寫后的count值寫入內存中(步驟3)線程A讀入count值為0 (步驟1) -》 改寫count值為1(步驟2) -》 將改寫后的count值寫入內存中(步驟3)
因為線程A還沒有更新改寫count的值到內存,這時線程B讀入count的值仍舊是0,導致最后2個線程結束后count的值為1。由此可見做了2次自增的操作,期望結果是2,但實際結果可能是1.這也是線程不安全的情況下,自增的操作的實際結果往往比期望結果小的原因。
下篇準備將講什么情況是線程不安全的。
新聞熱點
疑難解答
圖片精選