Java中synchronized的使用

貼一下關於synchronized的帖子:https://www.jianshu.com/p/d53bf830fa09
PS:個人覺得寫的非常不錯,非常推薦閱讀,有助於對多線程以及JMM的理解。

下面是我對synchronized的使用測試:

一、synchronized(this)

說明:synchronized用來同步自身對象
這個用法估計是很多初學者經常看到的用法(說的我自己好像不是初學者似的)。
下面就來猜下以下程序的執行結果吧。

執行類MainTest:

public class MainTest {

    public static void main(String[] args) throws InterruptedException {
        // 創建對象o1,o2
        TestObject o1 = new TestObject();
        TestObject o2 = new TestObject();

        // 創建線程t1,t2
        ThreadTest t1 = new ThreadTest(o1);
        ThreadTest t2 = new ThreadTest(o2); // TODO 將o2替換成o1試試看

        t1.start();
        t2.start();

        // 等待線程t1,t2執行完成
        t1.join();
        t2.join();

        // t1,t2都執行完成之後,最終輸出結果
        System.out.println(TestObject.i);
    }
}

測試對象類ObjectTest:

public class ObjectTest {
    public static int i = 0;

    /**
     * this
     */
    public void add() {
        synchronized (this) {
            for (int k = 0; k < 100000; k++) {
                i++;
            }
        }
    }
}

線程實現類ThreadTest:

public class ThreadTest extends Thread {

    private ObjectTest objectTest;

    public ThreadTest(ObjectTest objectTest) {
        this.objectTest = objectTest;
    }

    @Override
    public void run() {
        // this
        objectTest.add();
    }
}

運行結果:多次運行執行類之後,發現最終的結果值始終<200000;按照TODO裏操作後,最終值都=200000。

二、synchronized method

說明:synchronized用來聲明方法。
執行類和線程實現類不動,只修改測試對象類,繼續猜猜執行結果吧。

測試對象類ObjectTest:

public class ObjectTest {
    public static int i = 0;

    // TODO 按照一里的TODO修改試試看
    public synchronized void add() {
        for (int k = 0; k < 100000; k++) {
            i++;
        }
    }
}

運行結果:多次運行之後,最終結果依然是<200000。按照TODO修改之後,最終值都=200000。

三、synchronized(class)

說明:synchronized用來同步類的對象類,即Class<T> clazz = T.class中的clazz,描述類對象信息的。
執行類和線程實現類不動,只修改測試對象類,繼續猜猜執行結果吧。

測試對象類ObjectTest:

public class ObjectTest {
    public static int i = 0;

    public void add() {
        synchronized (ObjectTest.class) {
            for (int k = 0; k < 100000; k++) {
                i++;
            }
        }
    }
}

運行結果:多次運行之後,運行結果都=200000。

四、static method synchronized代碼塊

說明:靜態方法中使用synchronized代碼塊
執行類和線程實現類不動,只修改測試對象類。

測試對象類ObjectTest:

public class ObjectTest {
    public static int i = 0;

    public static void add() {
        synchronized (ObjectTest.class) {
            for (int k = 0; k < 100000; k++) {
                i++;
            }
        }
    }
}

運行結果:多次運行之後,運行結果都=200000。

五、static synchronized method

說明:synchronized用來聲明靜態方法
執行類和線程實現類不動,只修改測試對象類。

測試對象類ObjectTest:

public class ObjectTest {
    public static int i = 0;

    public static synchronized void add6() {
        for (int k = 0; k < 100000; k++) {
            i++;
        }
    }
}

運行結果:多次運行之後,運行結果都=200000。

六、synchronized(object)

說明:synchronized同步對象,其實這個本質和一是一樣的,但是同步對象有區別。我下面例子直接用String來替代。

線程實現類不動,修改測試對象類,執行類。

測試對象類ObjectTest:

public class ObjectTest {
    public static int i = 0;
    private String key;

    /**
     * 鎖key
     */
    public void add() {
        String key = this.key;
        synchronized (key) {
            for (int k = 0; k < 100000; k++) {
                i++;
            }
        }
    }

    // Getters And Setters
    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}

執行類MainTest:

public class MainTest {

    public static void main(String[] args) throws InterruptedException {
        // 創建對象o1,o2
        ObjectTest o1 = new ObjectTest();
        o1.setKey("A");
        ObjectTest o2 = new ObjectTest();
        o2.setKey("A"); // TODO 修改B試試看

        // 創建線程t1,t2
        ThreadTest t1 = new ThreadTest(o1);
        ThreadTest t2 = new ThreadTest(o2);

        t1.start();
        t2.start();

        // 等待線程t1,t2執行完成
        t1.join();
        t2.join();

        // t1,t2都執行完成之後,最終輸出結果
        System.out.println(ObjectTest.i);
    }
}

運行結果:多次運行之後,運行結果都=200000;按照TODO修改之後,多次運行結果都<200000。

七、總結

一句話總結:修改同一對象時,多個線程訪問同一方法,並且取得相同的鎖,這時候是線程安全的。

【一】裏面兩個線程雖然訪問同一個方法,但是取得的鎖卻不是同一個,因爲o1,o2不是同一個對象,所以這時候線程是不安全的。改成TODO裏的寫法之後,取得的鎖是同一對象,所以這時候線程是安全的。

【二】、【六】的道理和【一】是一樣的。

【三】兩個線程訪問同一個方法,獲取的鎖是類的類對象,一個類對象就是對類本身的描述,這個肯定是相同的,所以也是線程安全的。

【四】、【五】的道理和【三】一樣。

最後,如果多線程訪問通過一個對象,並修改該對象,要保證線程安全,其中一種實現手法是:對該對象加鎖,即當一個線程拿到該對象鎖之後,其它線程要等待該線程釋放鎖之後,才能繼續操作。這個實現手法延伸到分佈式開發,也是一樣的,只不過這個時候需要協調程序來進行協調了,比如zookeeper的分佈式鎖。還有一種實現手法是:CAS,ConcurrentHashMap裏,進行擴容的時候,好像就有用的這種實現手法。對於線程安全,我知道的就這兩種實現手法,而且我只是大概的瞭解它們,並沒有實際的去實現過,哪怕是簡單的實現也沒有,之後會嘗試簡單的去實現它們。

注:上面的純屬個人理解,包括那句總結的話。理解不對或有疑問的地方,請多多指教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章