網上有許多關於synchronized關鍵字用法的文章,發現有些文章里介紹的用法和場景,這里我整理一下,通過實踐,以一種有別於傳統的寫法介紹這個關鍵字的用法!用圖文並茂的方式展示出來,希望大家理解起來更加簡單易懂。本人知識有限,不足或錯誤的地方,歡迎指正,謝謝。
准備個實際測試用的例子
public class TestProgram extends Thread { static int j = 0; public static synchronized void testA(){ j++; System.out.println(Thread.currentThread().getName() + ": " + j); } public synchronized void testB(){ j++; System.out.println(Thread.currentThread().getName() + ": " + j); } public void testC(){ System.out.println("into testC..."); synchronized (this) { j++; System.out.println(Thread.currentThread().getName() + ": " + j); } } @Override public void run() { testA(); testB(); testC(); }
這就是這個例子的所有代碼,代碼簡單。這里簡短介紹一下:
總共有三個測試方法,分別是testA、testB和testC。testA方法是一個加了synchronized關鍵字和static關鍵字的方法,testB是只加了synchronized關鍵字的方法,testC是代碼塊中包含了synchronized塊的方法,這里分別用兩種模擬環境測試這三個方法的不同,用來區分三個方法在不同場景下的不同!
首先是測試環境(兩個線程跑同一個program,我在這里只創建了一個TestProgram實例)
同一實例下測試
public static void main(String[] args) { TestProgram program = new TestProgram(); Thread t = new Thread(program); Thread t2 = new Thread(program); t.start(); t2.start(); }
測試testA()
這里我用兩個線程去測試,通過start()調用run()方法,我在testA()方法里打了個斷點,然后兩個線程跑起來,我們看結果
我們在debug模式下,用多線程調試看結果,可以看到生成了Thread-1和Thread-2兩個線程,在紅色標記的代碼中,可以看到Thread-1進入了testA()方法,而Thread-2則沒有進入到testA()方法,這說明synchronized起到了同步作用。
接着我讓線程Thread-1跑完testA()方法后,Thread-2方法就直接被激活了,直接進入testA()方法,同步有效了。
得到第一個結論,在同一個實例下,synchronized關鍵字是對static方法起到同步作用的。
當Thread-1和Thread-2都跑完testA()方法后輸出結果:
Thread-1: 1
Thread-2: 2
為什么這個結論前面要加一個同一個實例?后面會看到,不在同一個實例下的多線程,synchronized關鍵效果不一樣!
接下來是進入testB()方法
當Thread-1進入testB()方法時,看Thread-2線程進入了一種叫stepping的模式(類似於等待),在進入這個等待的過程中,如果我們將Thread-1線程跑完testB()方法,那么Thread-2的狀態將由stepping改成suspended。簡單的說就是當一個線程進入testB()時,其他線程都在等這個線程跑完。
在這里發現synchronized方法也起到了同步的效果。
再看測試testC()方法的效果
我們可以看到,兩個線程都進入到了testC()方法,而且兩個線程的狀態都是suspended。接下來,當線程Thread-1進入到synchronized代碼塊中,看結果
可以看到當線程Thread-1進入代碼塊后,Thread-2再進入這個代碼塊,狀態就變成了stepping等待的情況了。說明這里synchronized方式同步有效。
最后執行testC()方法后的結果是:
into testC...
into testC...
Thread-1: 5
Thread-2: 6
以上就是在同一個實例下跑出來的結果。但如果是這種情況下,情況就不同了,這也是我們經常遇到的問題,有時候感覺怎么與以前的理論相駁,接着分析
不同實例下的測試
public static void main(String[] args) { TestProgram program = new TestProgram(); TestProgram program2 = new TestProgram(); Thread t = new Thread(program); Thread t2 = new Thread(program2); t.start(); t2.start(); }
換了兩行代碼,在兩個相同的TestProgram中,創建兩個對象,與上面的寫法差異在兩個線程跑各自的實例,這個時候synchronized的作用效果就不太一樣了
首先是testA()方法
測試的結果是
Thread-3: 1
Thread-2: 2
這個結果和開始測試的結果一樣,說明在這里synchronized起到了同步效果!
測試testB()
這里結果就和上面測試testB()效果就不一樣了,我們看效果
從圖中可以看到,兩個線程都同時進入了testB()方法中,也就是synchronized關鍵字沒起到同步兩個線程的作用
這里得出一個結論:兩個不同的實例,在不同的線程環境下,synchronized並不能實現同步效果,當然,static方法除外
后面的testC()方法也一樣,看看效果
兩個線程都同時進入了第22行代碼,就是同時在synchronized塊中,說明這個synchronized代碼塊沒有起到同步的效果。
綜合以上所訴,我得出一個這樣的結論:
如果多線程是對同一個對象的引用,那么synchronized關鍵字在所有的情況下都有同步的效果,如果多線程是對同一個對象的不同引用(創建了多個對象),除了static方法上面加的synchronized方法有同步引用的效果外,其他的synchronized(包括synchronized代碼塊)將沒有同步的效果。
至於為什么static方法上加synchronized方法,在不同的場景下都有效,原因是:被static修飾的成員變量和成員方法獨立於該類的任何對象,static方法不屬於類創建的引用,所以無論創建了多少個對象,對於static方法而言,都跟它沒關系。
ps:synchronized(this)這個this就是指對象的引用,這里指我當前線程的對象引用。
我的疑問:為什么創建一個對象調試的時候,是Thread-1和Thread-2,而我創建兩個對象調試的時候卻是Thread-2和Thread-3?