1. 程式人生 > >Java synchronized鎖物件和鎖非靜態成員變數的實驗

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

syncFunc.Thread: B enter: 1542010391798 syncFunc.Thread: B content old: ThreadC syncFunc.Thread: B content new: ThreadB syncFunc.Thread: B content final: ThreadB syncFunc.Thread: B exit: 1542010393799

從上述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與成員變數配對。
  • 鎖物件中巢狀呼叫鎖物件的非靜態成員變數,也沒有達到與單獨呼叫鎖物件的非靜態成員變數形成同步的效果(原因待研究)。
  • 另外,巢狀用鎖,要注意避免死鎖;儘量不要巢狀。

參考: