synchronized 關(guān)鍵字 用法

在Java的集合類中,有些是同步不安全的,如HashMap,有些是同步安全的,如HashTable,Vector。
查看源碼會(huì)發(fā)現(xiàn),這些所謂同步安全的集合,其實(shí)現(xiàn)方式 就是 通過在某些方法上,如add,增加synchronized修飾。然后同步就實(shí)現(xiàn)了。

同步是什么,同步就是排隊(duì),比如公共電話亭打電話,一個(gè)電話,現(xiàn)在來了3個(gè)人,同步就是說第一個(gè)人打完了,第二個(gè)人再上,之后第三個(gè)人再上。

需要同步的原因是因?yàn)橛胁l(fā),有多個(gè)線程,單線程永遠(yuǎn)沒有同步的問題,那本身就是同步的。
多線程也未必就有同步的需要,當(dāng)眾多線程 對(duì)于 觀察對(duì)象,不進(jìn)行干擾的話,并發(fā)也不需要同步,比如看直播,1個(gè)人看直播,和10萬人看直播,你們只是看的話,是沒有并發(fā)問題的,不需要同步,只需要把直播畫面,拷貝10萬份給每個(gè)人就可以了。

但下面這種情況就有問題了,10個(gè)人在看一個(gè)主播直播,其中3個(gè)人希望主播換一首歌,換成自己喜歡的,如果這三個(gè)請(qǐng)求同時(shí)提出,那么主播該怎么辦。
如果三首同時(shí)放,那不可能,也沒法聽。
如果隨便忽略掉請(qǐng)求,也不算完美的解決辦法。
最后的辦法只能是 把并行的需求,串行化,一個(gè)一個(gè)來。

所以同步說白一點(diǎn),就是排隊(duì),一個(gè)一個(gè)來。

那一個(gè)一個(gè)來有什么問題呢? 這不又成單線程了嗎

所以這其中的問題是
1,單線程,正確,但一個(gè)一個(gè)來,處理的太慢。
2,多線程,并發(fā),同時(shí)做,肯定速度快,但某些點(diǎn)上,有可能出現(xiàn)錯(cuò)誤。

同步就要求,即快,又正確。這就是難點(diǎn),難在,需要分析出,
在這件事情中,哪些區(qū)域是可以并發(fā),也不會(huì)出問題的,哪些區(qū)域是不能并發(fā),還是要排隊(duì)的。

所以同步就是 多線程+單線程的組合。

用關(guān)鍵字synchronized,首先要分析出,要對(duì)誰進(jìn)行同步,先看個(gè)錯(cuò)誤例子,這里面用了synchronized,還是沒有達(dá)到同步的效果。


class Task implements Runnable{

    synchronized void add(){

            int tmp = TestMain.i;
            tmp = tmp + 1;
            TestMain.i = tmp;
    }

    public void run() {
        //每個(gè)線程會(huì)增加50次
        for(int i=0;i<50;i++){
            add();
        }
    }
}

public class TestMain {

    static int i=0;
    static Object o = new Object();

    public static void main(String[] args) throws InterruptedException {

//        創(chuàng)建100個(gè)線程
        for(int i=0;i<100;i++){
            Thread t =  new Thread(new Task());
            t.start();
        }

        Thread.sleep(1000);
        System.out.println(i);

    }
}

某一次輸出為
4969

這個(gè)程序會(huì)創(chuàng)建100個(gè)線程,每個(gè)線程執(zhí)行的任務(wù)是 對(duì) 變量i增加50次, 所以我期望的結(jié)果,i最后變成5000了,但事與愿違
這個(gè)程序基本每次輸出都不一樣,但都小于5000.

原因就處在add()方法上, 但我已經(jīng)給這個(gè)方法 加上synchronized關(guān)鍵字了,為什么還是同步不了呢?

這個(gè)問題困擾了我很久,直到---
因?yàn)槲覜]搞明白一個(gè)問題,synchronized是給誰加鎖的。
鎖在哪兒, Java中,每個(gè)對(duì)象都是鎖,包括實(shí)例出來的對(duì)象,類本身。

現(xiàn)在回頭看這個(gè)程序, 我把synchronized加再一個(gè) 普通方法上, 這時(shí) 鎖就是 實(shí)例化的Task對(duì)象, 它能實(shí)現(xiàn)的效果是, 多個(gè)線程,在操作這個(gè)實(shí)例的 add方法時(shí),保證它們是排隊(duì)進(jìn)行的。
可我的程序中
Thread t = new Thread(new Task()); 這段代碼, 在創(chuàng)建每個(gè)線程時(shí),都新創(chuàng)建 了一個(gè) 新的Task實(shí)例, 等于是100個(gè)線程,創(chuàng)建了100把鎖,足球場(chǎng)100個(gè)人,發(fā)了100個(gè)球,各玩各的,根本不同競(jìng)爭(zhēng)。 那么怎么修改呢
簡(jiǎn)單,只創(chuàng)建一個(gè) Task實(shí)例,給100個(gè)線程去玩,到時(shí)候,自然會(huì)競(jìng)爭(zhēng)add方法的, 代碼修改如下


class Task implements Runnable{

    synchronized void add(){

            int tmp = TestMain.i;
            tmp = tmp + 1;
            TestMain.i = tmp;
    }

    public void run() {
        //每個(gè)線程會(huì)增加50次
        for(int i=0;i<50;i++){
            add();
        }
    }
}

public class TestMain {

    static int i=0;
    static Object o = new Object();

    public static void main(String[] args) throws InterruptedException {
        //只創(chuàng)建一個(gè)Task ,然后傳給100個(gè)線程。
        Task  task = new Task();
//        創(chuàng)建100個(gè)線程
        for(int i=0;i<100;i++){
            Thread t =  new Thread(task);
            t.start();
        }

        Thread.sleep(1000);
        System.out.println(i);

    }
}

輸出
5000

輸出5000, 符合預(yù)期。 這是synchronized的一種情況,對(duì)普通方法加鎖,鎖是該類實(shí)例后的 對(duì)象。

synchronized還有兩種用法, 修飾靜態(tài)方法, 和 修飾代碼塊。

下面的代碼是 同步靜態(tài)方法 的 例子,將原來的add方法改成static的了


class Task implements Runnable{

    synchronized static void add(){

            int tmp = TestMain.i;
            tmp = tmp + 1;
            TestMain.i = tmp;
    }

    public void run() {
        //每個(gè)線程會(huì)增加50次
        for(int i=0;i<50;i++){
            add();
        }
    }
}

public class TestMain {

    static int i=0;
    static Object o = new Object();

    public static void main(String[] args) throws InterruptedException {
        //只創(chuàng)建一個(gè)Task ,然后傳給100個(gè)線程。

//        創(chuàng)建100個(gè)線程
        for(int i=0;i<100;i++){
            Thread t =  new Thread(new Task());
            t.start();
        }

        Thread.sleep(1000);
        System.out.println(i);

    }
}

輸出
5000  符合預(yù)期。

在這個(gè)例子中, 100個(gè)線程 創(chuàng)建了100個(gè)Task實(shí)例, 但并沒有出現(xiàn) 并發(fā)的錯(cuò)誤, 原因是 add是靜態(tài)方法, 和具體的實(shí)例無關(guān), 即使new100個(gè)實(shí)例,也是用的一個(gè)方法, synchronized修飾靜態(tài)方法, 對(duì)應(yīng)的鎖是這個(gè)靜態(tài)方法的類,所以 同步?jīng)]有問題。

第三種情況, synchronized代碼塊


class Task implements Runnable{

    void add(){
        synchronized (Task.class) {
            int tmp = TestMain.i;
            tmp = tmp + 1;
            TestMain.i = tmp;
        }
    }

    public void run() {
        //每個(gè)線程會(huì)增加50次
        for(int i=0;i<50;i++){
            add();
        }
    }
}

public class TestMain {

    static int i=0;
    static Object o = new Object();

    public static void main(String[] args) throws InterruptedException {


//        創(chuàng)建100個(gè)線程
        for(int i=0;i<100;i++){
            Thread t =  new Thread(new Task());
            t.start();
        }

        Thread.sleep(1000);
        System.out.println(i);

    }
}

這次add還是普通方法,而add的函數(shù)體 都包含在 synchronized中,
這樣寫的時(shí)候, synchronized后面有個(gè)括號(hào), 其中需要一個(gè)對(duì)象,也就是鎖, 這樣的好處是 這個(gè)鎖可以根據(jù)需要, 傳不同的對(duì)象,前面兩種方式 一個(gè)是 所在實(shí)例的作為鎖,一個(gè)是所在類作為鎖,有些不靈活, 第三種方法,你可以將任何對(duì)象傳進(jìn)來,這其實(shí)是為了 縮小鎖定的范圍。

畢竟鎖定和多線程是矛盾的。一定要選擇合適的 同步范圍 。
正確性是第一優(yōu)先的, 如果程序無法保證正確,那就無法使用,
在保證正確性的前提下, 縮小鎖定的范圍, 提高程序的整體效率,是追求的目標(biāo)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Synchronized 我們?cè)谑褂弥?,常用的都是使用同步代碼塊,如下: 那么其實(shí)我們知道,synchronize...
    行人墨客閱讀 3,757評(píng)論 0 2
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,897評(píng)論 0 11
  • 第三章 Java內(nèi)存模型 3.1 Java內(nèi)存模型的基礎(chǔ) 通信在共享內(nèi)存的模型里,通過寫-讀內(nèi)存中的公共狀態(tài)進(jìn)行隱...
    澤毛閱讀 4,502評(píng)論 2 21
  • 對(duì)我來說,你只是個(gè)小男孩,就像其他成千上萬個(gè)小男孩一樣沒有什么兩樣。我不需要你你也不需要我。對(duì)你來說,我也只是一只...
    再見是藍(lán)閱讀 1,032評(píng)論 0 0
  • 如果說人生像是調(diào)味品 那我的人生 酸甜苦辣 似乎都是你 酸是我們的相識(shí) 拘謹(jǐn)?shù)目拷Y貌的疏離 甜是我們的相知 歡樂...
    安之若素cccl閱讀 190評(píng)論 0 1

友情鏈接更多精彩內(nèi)容