多線程併發與String的內存模型介紹,並完美解決鎖的併發問題

       首先需要了解String的內存模型,有常量池,堆,那麼何時訪問的是常量池,何時又是訪問的堆,需要提前瞭解,當然下文也會介紹,此處就不在過多說明;另外線程的內存模型也要了解一下,尤其是線程棧。接下來我們就來做一個簡單的實驗,代碼如下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        final Test1 test1 = new Test1();
        CountDownLatch latch = new CountDownLatch(20);
        for(int i=0;i<20;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        String a = new String("1");
                        test1.add(a);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    latch.countDown();
                }
            }).start();
        }
        latch.await();
        System.out.println(test1.a);
       
    }
    
}

class Test1{
    public int a =0;
    void add(String i) throws InterruptedException {
        synchronized (i){
            int c=a+1;
            Thread.sleep(200);
            a=c;
        }
    }
}

從上面看,開啓20個線程,分別對a進行加1操作,覺得這段代碼的執行結果會是多少?是20嗎?顯然不是。此處new String會在內存的堆中單獨創建對象,每個線程都是各自的對象,然後在synchronize中對這個對象加鎖,其實是互不競爭的,所以最後的運行結果可能是1,也可能是其他。那麼如何修改才能得到結果20呢?那就是讓20個線程相互競爭,可以通過String的intern獲取常量池中相同內容的對象,然後對該對象加鎖,也就是說,只要內容相同,返回的對象就是同一個,然後用synchronize加鎖就可以成功,只需調整一下同步塊的代碼即可:

synchronized (i.intern())

此時獲取到結果就是20。

有的人可能會說,直接在方法上同步就可以了,當然這種方式也是可以的,不過性能肯定要差,主要體現在兩方面來說:

1、對方法加同步,那整個方法執行完成後才被其他線程執行,鎖的粒度太大了。

2、鎖的範圍太大了,此次測試只是將i設置成爲相同了,所以20個線程都對這個i競爭,如果i不同,方法上的同步也會造成20個線程競爭;而採用i.intern(),可以把鎖給分散開,範圍更小,性能就會更好。

 

 

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