Java synchronized鎖物件和鎖非靜態成員變數的實驗
關於Java synchronized,網上博文對主要概念都解釋的很清楚,但對鎖物件和鎖物件的非靜態成員變數的區別,或者沒有提到,或者講述的不是特別清晰、深刻。
根據本人的理解和實驗效果,我認為Java synchronized的主要用法分三種:
- 鎖靜態函式或類.class(即所謂的類鎖)
- 鎖物件/鎖函式(本文簡稱物件鎖)
- 鎖物件的非靜態成員變數(本文簡稱變數鎖)
本文對第1種用法不做討論,僅討論後兩種用法的區別。
實驗環境:win10 + intelliJ
1. 物件鎖與變數鎖分開使用,並不能實現同步保護
public class JavaSync { public static void main(String[] args) { SyncContent syncContent = new SyncContent("JavaSync"); ThreadA a = new ThreadA(syncContent); a.setName("A"); a.start(); ThreadB b = new ThreadB(syncContent); b.setName("B"); b.start(); ThreadC c = new ThreadC(syncContent); c.setName("C"); c.start(); } } class SyncContent { String content = new String(); public SyncContent(String content){ this.content = content; } synchronized public void syncFunc(String str){ try { System.out.println("syncFunc.Thread: " + Thread.currentThread().getName() + " enter: " + System.currentTimeMillis()); System.out.println("syncFunc.Thread: " + Thread.currentThread().getName() + " content old: " + content); content = str; System.out.println("syncFunc.Thread: " + Thread.currentThread().getName() + " content new: " + content); Thread.sleep(2000); System.out.println("syncFunc.Thread: " + Thread.currentThread().getName() + " content final: " + content); System.out.println("syncFunc.Thread: " + Thread.currentThread().getName() + " exit: " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } public void syncThis(String str){ synchronized(this) { try { System.out.println("syncThis.Thread: " + Thread.currentThread().getName() + " enter: " + System.currentTimeMillis()); System.out.println("syncThis.Thread: " + Thread.currentThread().getName() + " content old: " + content); content = str; System.out.println("syncThis.Thread: " + Thread.currentThread().getName() + " content new: " + content); Thread.sleep(2000); System.out.println("syncThis.Thread: " + Thread.currentThread().getName() + " content final: " + content); System.out.println("syncThis.Thread: " + Thread.currentThread().getName() + " exit: " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } public void syncVariable(String str) { synchronized(content) { System.out.println("syncVariable.Thread: " + Thread.currentThread().getName() + " enter: " + System.currentTimeMillis()); System.out.println("syncVariable.Thread: " + Thread.currentThread().getName() + " content old: " + content); content = str; System.out.println("syncVariable.Thread: " + Thread.currentThread().getName() + " content new: " + content); System.out.println("syncVariable.Thread: " + Thread.currentThread().getName() + " exit: " + System.currentTimeMillis()); } } } class ThreadA extends Thread { private SyncContent syncContent; private String me = "ThreadA"; public ThreadA(SyncContent syncContent) { super(); this.syncContent = syncContent; } @Override public void run() { syncContent.syncThis(me); } } class ThreadB extends Thread { private SyncContent syncContent; private String me = "ThreadB"; public ThreadB(SyncContent syncContent) { super(); this.syncContent = syncContent; } @Override public void run() { syncContent.syncFunc(me); } } class ThreadC extends Thread { private SyncContent syncContent; private String me = "ThreadC"; public ThreadC(SyncContent syncContent) { super(); this.syncContent = syncContent; } @Override public void run() { syncContent.syncVariable(me); } }
Log如下:
syncThis.Thread: A enter: 1542010389797 syncThis.Thread: A content old: JavaSync syncThis.Thread: A content new: ThreadA syncVariable.Thread: C enter: 1542010389798 syncVariable.Thread: C content old: ThreadA syncVariable.Thread: C content new: ThreadC syncVariable.Thread: C exit: 1542010389798 syncThis.Thread: A content final: ThreadC syncThis.Thread: A exit: 1542010391798
從上述log可見:
- synchronized(this) 與 synchronized Function 的功能一致,都是鎖物件。
- synchronized(this)並不會與synchronized(content)形成同步,兩者並沒有包含關係。即使content是this裡包含的成員,但對於synchronized()來講,是兩個不同的輸入物件或者說引數,二者不會同步。
2. 物件鎖與變數鎖巢狀使用,也不能實現同步保護
把syncThis()和syncFunc()函式裡巢狀synchronized (content):
public void syncThis(String str){ synchronized(this) { try { System.out.println("syncThis.Thread: " + Thread.currentThread().getName() + " enter: " + System.currentTimeMillis()); System.out.println("syncThis.Thread: " + Thread.currentThread().getName() + " content old: " + content); synchronized (content) { content = str; System.out.println("syncThis.Thread: " + Thread.currentThread().getName() + " content new: " + content); Thread.sleep(2000); } System.out.println("syncThis.Thread: " + Thread.currentThread().getName() + " content final: " + content); System.out.println("syncThis.Thread: " + Thread.currentThread().getName() + " exit: " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } }
Log如下:
syncThis.Thread: A enter: 1542012050428 syncThis.Thread: A content old: JavaSync syncThis.Thread: A content new: ThreadA syncVariable.Thread: C enter: 1542012050429 syncVariable.Thread: C content old: ThreadA syncVariable.Thread: C content new: ThreadC syncVariable.Thread: C exit: 1542012050429 syncThis.Thread: A content final: ThreadC syncThis.Thread: A exit: 1542012052428 syncFunc.Thread: B enter: 1542012052428 syncFunc.Thread: B content old: ThreadC syncFunc.Thread: B content new: ThreadB syncFunc.Thread: B content final: ThreadB syncFunc.Thread: B exit: 1542012054429
從上述log可見:即使synchronized(this)中巢狀呼叫synchronized(content),也沒有與單獨呼叫synchronized(content)達到同步的效果。原因待研究。(曾猜測是不是Thread.sleep會釋放鎖,但把sleep換成一個純做加減乘除運算的耗時函式後,效果也一樣。另外,一些文章也講了sleep並不會釋放執行緒持有的鎖。)
另外實驗,如果ThreadB的run()裡面改成呼叫syncVariable(me),是可以與ThreadC形成同步的,這個結果符合預期,不列log。
結論:
- 鎖物件與鎖物件的非靜態成員變數並不會形成同步,兩者並沒有包含關係。即使this裡包含content成員,但對於synchronized()來講,是兩個不同的輸入物件或者說引數,二者不會同步。
- 如果要對類物件裡多個成員變數分別進行同步的,需保持同步引數的一致,即this與this配對(或者this與函式配對),變數與變數各自配對,但不能this與成員變數配對。
- 鎖物件中巢狀呼叫鎖物件的非靜態成員變數,也沒有達到與單獨呼叫鎖物件的非靜態成員變數形成同步的效果(原因待研究)。
- 另外,巢狀用鎖,要注意避免死鎖;儘量不要巢狀。
參考: