国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java synchronized 多線程同步問題詳解

Eidesen / 2116人閱讀

摘要:同步代碼塊二類,鎖是小括號(hào)中的類對(duì)象對(duì)象。因?yàn)閷?duì)于同一個(gè)實(shí)例對(duì)象,各線程之間訪問其中的同步方法是互斥的。優(yōu)化同步代碼塊的方式有,減少同步區(qū)域或減小鎖的范圍。

版權(quán)聲明:本文由吳仙杰創(chuàng)作整理,轉(zhuǎn)載請(qǐng)注明出處:https://segmentfault.com/a/1190000009225706

1. 引言

在 Java 多線程編程中,我們常需要考慮線程安全問題,其中關(guān)鍵字 synchronized 在線程同步中就扮演了非常重要的作用。

下面就對(duì) synchronized 進(jìn)行詳細(xì)的示例講解,其中本文構(gòu)建 thread 的寫法是采用 Java 8 新增的 Lambda 表達(dá)式。如果你對(duì) Lambda 表達(dá)式還不了解,可以查看我之前的文章《Java 8 Lambda 表達(dá)式詳解》。

2. synchronized 鎖的是什么

首先我們明確一點(diǎn),synchronized 鎖的不是代碼,鎖的都是對(duì)象

鎖的對(duì)象有以下幾種:

同步非靜態(tài)方法(synchronized method),鎖是當(dāng)前對(duì)象的實(shí)例對(duì)象

同步靜態(tài)方法(synchronized static method),鎖是當(dāng)前對(duì)象的類對(duì)象(Class 對(duì)象)

同步代碼塊一(synchronized (this)synchronized (類實(shí)例對(duì)象)),鎖是小括號(hào) () 中的實(shí)例對(duì)象

同步代碼塊二(synchronized (類.class)),鎖是小括號(hào) () 中的類對(duì)象(Class 對(duì)象)

2.1 實(shí)例對(duì)象鎖與類對(duì)象鎖

1)實(shí)例對(duì)象鎖,不同的實(shí)例擁有不同的實(shí)例對(duì)象鎖,所以對(duì)于同一個(gè)實(shí)例對(duì)象,在同一時(shí)刻只有一個(gè)線程可以訪問這個(gè)實(shí)例對(duì)象的同步方法;不同的實(shí)例對(duì)象,不能保證多線程的同步操作。

2)類對(duì)象鎖(全局鎖),在 JVM 中一個(gè)類只有一個(gè)與之對(duì)應(yīng)的類對(duì)象,所以在同一時(shí)刻只有一個(gè)線程可以訪問這個(gè)類的同步方法。

3. 示例分析 3.1 同步非靜態(tài)實(shí)例方法

同步非靜態(tài)方法,實(shí)際上鎖定的是當(dāng)前對(duì)象的實(shí)例對(duì)象。在同一時(shí)刻只有一個(gè)線程可以訪問該實(shí)例的同步方法,但對(duì)于多個(gè)實(shí)例的同步方法,不同實(shí)例之間對(duì)同步方法的訪問是不受同步影響(synchronized 同步失效)。

首先我們嘗試下,synchronized 同步失敗的情況:

package com.wuxianjiezh.demo.threadpool;

public class MainTest {

    public static void main(String[] args) {
        Bank xMBank = new Bank();
        Bank xGBank = new Bank();
        Bank xHBank = new Bank();
        Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
        Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小剛");
        Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小紅");
        xMThread.start();
        xGThread.start();
        xHThread.start();
    }
}

class Bank {

    private int money = 1000;

    public synchronized void deposit(Bank bank, int money) {
        // synchronized (this) { // 同步方法塊(實(shí)例對(duì)象)
        // synchronized (bank) { // 同步方法塊(實(shí)例對(duì)象)
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "--當(dāng)前銀行余額為:" + this.money);
            this.money += money;
            System.out.println(threadName + "--存入后銀行余額為:" + this.money);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        // }
    }
}

運(yùn)行結(jié)果:

小明--當(dāng)前銀行余額為:1000
小剛--當(dāng)前銀行余額為:1000
小明--存入后銀行余額為:1200
小紅--當(dāng)前銀行余額為:1000
小剛--存入后銀行余額為:1200
小紅--存入后銀行余額為:1200

從上面的運(yùn)行結(jié)果,我們發(fā)現(xiàn)對(duì) Bankmoney 的操作并沒有同步,synchronized 失效了?

這是因?yàn)閷?shí)例對(duì)象鎖,只對(duì)同一個(gè)實(shí)例生效,對(duì)同一個(gè)對(duì)象的不同實(shí)例不保證同步。

修改上述代碼,實(shí)現(xiàn)同步操作。這里將有兩種方案:只實(shí)例化一個(gè)或在方法塊中的鎖定類對(duì)象。

方案一、多個(gè)線程只對(duì)同一個(gè)實(shí)例對(duì)象操作:

package com.wuxianjiezh.demo.threadpool;

public class MainTest {

    public static void main(String[] args) {
        Bank xMBank = new Bank();
        // Bank xGBank = new Bank();
        // Bank xHBank = new Bank();
        Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
        Thread xGThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小剛");
        Thread xHThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小紅");
        xMThread.start();
        xGThread.start();
        xHThread.start();
    }
}

class Bank {

    private int money = 1000;

    public synchronized void deposit(Bank bank, int money) {
        // synchronized (this) { // 同步方法塊(實(shí)例對(duì)象)
        // synchronized (bank) { // 同步方法塊(實(shí)例對(duì)象)
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "--當(dāng)前銀行余額為:" + this.money);
            this.money += money;
            System.out.println(threadName + "--存入后銀行余額為:" + this.money);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        // }
    }
}

運(yùn)行結(jié)果:

小明--當(dāng)前銀行余額為:1000
小明--存入后銀行余額為:1200
小紅--當(dāng)前銀行余額為:1200
小紅--存入后銀行余額為:1400
小剛--當(dāng)前銀行余額為:1400
小剛--存入后銀行余額為:1600
...

可以看到,結(jié)果正確執(zhí)行。因?yàn)閷?duì)于同一個(gè)實(shí)例對(duì)象,各線程之間訪問其中的同步方法是互斥的。

方案二、在方法塊中鎖定類對(duì)象:

package com.wuxianjiezh.demo.threadpool;

public class MainTest {

    public static void main(String[] args) {
        Bank xMBank = new Bank();
        Bank xGBank = new Bank();
        Bank xHBank = new Bank();
        Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
        Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小剛");
        Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小紅");
        xMThread.start();
        xGThread.start();
        xHThread.start();
    }
}

class Bank {

    private int money = 1000;

    public void deposit(Bank bank, int money) {
        synchronized (Bank.class) { // 全局鎖
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "--當(dāng)前銀行余額為:" + this.money);
            this.money += money;
            System.out.println(threadName + "--存入后銀行余額為:" + this.money);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

運(yùn)行結(jié)果:

小明--當(dāng)前銀行余額為:1000
小明--存入后銀行余額為:1200
小紅--當(dāng)前銀行余額為:1000
小紅--存入后銀行余額為:1200
小剛--當(dāng)前銀行余額為:1000
小剛--存入后銀行余額為:1200

思考:從結(jié)果中我們發(fā)現(xiàn),線程是同步操作了,但為什么在我們的 money 怎么才 1200 啊?

要回答上面問題也很簡(jiǎn)單,首先線程是同步操作了,這個(gè)沒有疑問,說明我們的全局鎖生效了,那為什么錢少了,因?yàn)槲覀冞@里 mew 了三個(gè)對(duì)象,三個(gè)對(duì)象都有各自的 money,他們并不共享,所以最后都是 1200,最終一共還是增加了 6000,錢一點(diǎn)沒有少喔。

那有沒有辦法,讓這些線程間共享 money 呢?方法很簡(jiǎn)單,只要設(shè)置 moneystatic 即可。

3.1.1 對(duì)同步代碼塊優(yōu)化的思考

對(duì)于一個(gè)方法,可能包含多個(gè)操作部分,而每個(gè)操作部分的消耗各不相同,而且并不是所有的操作都是需要同步控制的,那么,是否可以將那些影響效率,又不需要同步操作的內(nèi)容,提取到同步代碼塊外呢?

請(qǐng)看以下示例:

package com.wuxianjiezh.demo.threadpool;

public class MainTest {

    public static void main(String[] args) {
        Bank xMBank = new Bank();
        Bank xGBank = new Bank();
        Bank xHBank = new Bank();
        Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
        Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小剛");
        Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小紅");
        xMThread.start();
        xGThread.start();
        xHThread.start();
    }
}

class Bank {

    private int money = 1000;

    public void deposit(Bank bank, int money) {
        String threadName = Thread.currentThread().getName();
        synchronized (Bank.class) { // 同步方法塊(實(shí)例對(duì)象)
            System.out.println(threadName + "--當(dāng)前銀行余額為:" + this.money);
            this.money += money;
            System.out.println(threadName + "--存入后銀行余額為:" + this.money);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try {
            // 假設(shè)這里是非常耗時(shí),并且不需要同步控制的操作
            Thread.sleep(2000);
            System.out.println(threadName + "--和錢無(wú)關(guān),不需要同步控制的操作");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運(yùn)行結(jié)果:

小明--當(dāng)前銀行余額為:1000
小明--存入后銀行余額為:1200
小紅--當(dāng)前銀行余額為:1000
小紅--存入后銀行余額為:1200
小剛--當(dāng)前銀行余額為:1000
小剛--存入后銀行余額為:1200
小明--和錢無(wú)關(guān),不需要同步控制的操作
小紅--和錢無(wú)關(guān),不需要同步控制的操作
小剛--和錢無(wú)關(guān),不需要同步控制的操作

這時(shí)發(fā)現(xiàn),各線程雖然都有自己的實(shí)例化對(duì)象,但其中操作 money 的部分是同步的,對(duì)于與 money 無(wú)關(guān)的操作則又是異步的。

結(jié)論:可以通過減少同步區(qū)域來優(yōu)化同步代碼塊。

3.1.2 對(duì)同步代碼塊優(yōu)化的思考(進(jìn)階)

我們知道同步的對(duì)象不是實(shí)例對(duì)象就是類對(duì)象。現(xiàn)在假設(shè)一個(gè)類有多個(gè)同步方法,那么當(dāng)某個(gè)線程進(jìn)入其中一個(gè)同步方法時(shí),這個(gè)類的其它同步方法也會(huì)被鎖住,造成其它與當(dāng)前鎖定操作的同步方法毫無(wú)關(guān)系的同步方法也被鎖住,最后的結(jié)果就是影響了整個(gè)多線程執(zhí)行的性能,使原本不需要互斥的方法也都進(jìn)行了互斥操作。比如:

package com.wuxianjiezh.demo.threadpool;

public class MainTest {

    public static void main(String[] args) {
        Bank xMBank = new Bank();
        Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
        Thread xGThread = new Thread(xMBank::showInfo, "小剛");
        xMThread.start();
        xGThread.start();
    }
}

class Bank {

    private int money = 1000;

    public void deposit(Bank bank, int money) {
        long begin = System.currentTimeMillis();

        String threadName = Thread.currentThread().getName();
        synchronized (this) { // 同步方法塊(實(shí)例對(duì)象)
            this.money += money;
            try {
                System.out.println(threadName + "--當(dāng)前銀行余額為:" + this.money);
                // 模擬一個(gè)非常耗時(shí)的操作
                Thread.sleep(5000);
                System.out.println(threadName + "--存入后銀行余額為:" + this.money);

                long end = System.currentTimeMillis();
                System.out.println(threadName + "--存入耗時(shí):" + (end - begin));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 一個(gè)與資金操作沒有任務(wù)關(guān)系的同步方法
     */
    public void showInfo() {
        long begin = System.currentTimeMillis();

        String threadName = Thread.currentThread().getName();
        synchronized (this) {
            try {
                System.out.println(threadName + "--開始查看銀行信息");
                Thread.sleep(5000);
                System.out.println(threadName + "--銀行詳細(xì)信息...");

                long end = System.currentTimeMillis();
                System.out.println(threadName + "--查看耗時(shí):" + (end - begin));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

運(yùn)行結(jié)果:

小明--當(dāng)前銀行余額為:1200
小明--存入后銀行余額為:1200
小明--存入耗時(shí):5000
小剛--開始查看銀行信息
小剛--銀行詳細(xì)信息...
小剛--查看耗時(shí):10000

從運(yùn)行結(jié)果中,我們看到小剛這個(gè)線程平白無(wú)故多等了 5 秒鐘,嚴(yán)重影響了線程性能。

針對(duì)上面的情況,我們可以采用多個(gè)實(shí)例對(duì)象鎖的方案解決,比如:

package com.wuxianjiezh.demo.threadpool;

public class MainTest {

    public static void main(String[] args) {
        Bank xMBank = new Bank();
        Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
        Thread xGThread = new Thread(xMBank::showInfo, "小剛");
        xMThread.start();
        xGThread.start();
    }
}

class Bank {

    private int money = 1000;

    private final Object syncDeposit = new Object(); // 同步鎖
    private final Object syncShowInfo = new Object(); // 同步鎖

    public void deposit(Bank bank, int money) {
        long begin = System.currentTimeMillis();

        String threadName = Thread.currentThread().getName();
        synchronized (this.syncDeposit) { // 同步方法塊(實(shí)例對(duì)象)
            this.money += money;
            try {
                System.out.println(threadName + "--當(dāng)前銀行余額為:" + this.money);
                // 模擬一個(gè)非常耗時(shí)的操作
                Thread.sleep(5000);
                System.out.println(threadName + "--存入后銀行余額為:" + this.money);

                long end = System.currentTimeMillis();
                System.out.println(threadName + "--存入耗時(shí):" + (end - begin));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 一個(gè)與資金操作沒有任務(wù)關(guān)系的同步方法
     */
    public void showInfo() {
        long begin = System.currentTimeMillis();

        String threadName = Thread.currentThread().getName();
        synchronized (this.syncShowInfo) {
            try {
                System.out.println(threadName + "--開始查看銀行信息");
                Thread.sleep(5000);
                System.out.println(threadName + "--銀行詳細(xì)信息...");

                long end = System.currentTimeMillis();
                System.out.println(threadName + "--查看耗時(shí):" + (end - begin));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

運(yùn)行結(jié)果:

小剛--開始查看銀行信息
小明--當(dāng)前銀行余額為:1200
小剛--銀行詳細(xì)信息...
小明--存入后銀行余額為:1200
小明--存入耗時(shí):5000
小剛--查看耗時(shí):5000

我們發(fā)現(xiàn),兩個(gè)線程間同步被取消了,性能問題也解決了。

總結(jié):可以創(chuàng)建不同同步方法的不同同步鎖(減小鎖的范圍)來優(yōu)化同步代碼塊。

3.2 同步靜態(tài)方法

同步靜態(tài)方法,鎖的是類對(duì)象而不是某個(gè)實(shí)例對(duì)象,所以可以理解為對(duì)于靜態(tài)方法的鎖是全局的鎖,同步也是全局的同步。

package com.wuxianjiezh.demo.threadpool;

public class MainTest {

    public static void main(String[] args) {
        Bank xMBank = new Bank();
        Bank xGBank = new Bank();
        Bank xHBank = new Bank();
        Thread xMThread = new Thread(() -> xMBank.deposit(xMBank, 200), "小明");
        Thread xGThread = new Thread(() -> xGBank.deposit(xGBank, 200), "小剛");
        Thread xHThread = new Thread(() -> xHBank.deposit(xHBank, 200), "小紅");
        xMThread.start();
        xGThread.start();
        xHThread.start();
    }
}

class Bank {

    private static int money = 1000;

    public synchronized static void deposit(Bank bank, int money) {
        // synchronized (Bank.class) { // 全局鎖
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + "--當(dāng)前銀行余額為:" + Bank.money);
        Bank.money += money;
        System.out.println(threadName + "--存入后銀行余額為:" + Bank.money);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // }
    }
}

運(yùn)行結(jié)果:

小明--當(dāng)前銀行余額為:1000
小明--存入后銀行余額為:1200
小紅--當(dāng)前銀行余額為:1200
小紅--存入后銀行余額為:1400
小剛--當(dāng)前銀行余額為:1400
小剛--存入后銀行余額為:1600
4. 總結(jié)

同步鎖 synchronized 要點(diǎn):

synchronized 鎖的不是代碼,鎖的都是對(duì)象

實(shí)例對(duì)象鎖:同步非靜態(tài)方法(synchronized method),同步代碼塊(synchronized (this)synchronized (類實(shí)例對(duì)象))。

類對(duì)象(Class 對(duì)象)鎖:同步靜態(tài)方法(synchronized static method),同步代碼塊(synchronized (類.class))。

相同對(duì)象的不同的實(shí)例擁有不同的實(shí)例對(duì)象鎖,但類對(duì)象鎖(全局鎖)有僅只有一個(gè)。

優(yōu)化同步代碼塊的方式有,減少同步區(qū)域或減小鎖的范圍。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/69948.html

相關(guān)文章

  • synchronized關(guān)鍵字使用詳解

    摘要:基本使用同步代碼塊同步代碼塊延時(shí)秒,方便后面測(cè)試作用代碼塊時(shí),方法中的,是指調(diào)用該方法的對(duì)象。那么這個(gè)時(shí)候使用關(guān)鍵字就需要注意了推薦使用同步代碼塊,同步的代碼塊中傳入外部定義的一個(gè)變量。 簡(jiǎn)述 計(jì)算機(jī)單線程在執(zhí)行任務(wù)時(shí),是嚴(yán)格按照程序的代碼邏輯,按照順序執(zhí)行的。因此單位時(shí)間內(nèi)能執(zhí)行的任務(wù)數(shù)量有限。為了能在相同的時(shí)間內(nèi)能執(zhí)行更多的任務(wù),就必須采用多線程的方式來執(zhí)行(注意:多線程模式無(wú)法減...

    Jeffrrey 評(píng)論0 收藏0
  • 40道阿里巴巴JAVA研發(fā)崗線程面試題詳解,你能答出

    摘要:但是單核我們還是要應(yīng)用多線程,就是為了防止阻塞。多線程可以防止這個(gè)問題,多條線程同時(shí)運(yùn)行,哪怕一條線程的代碼執(zhí)行讀取數(shù)據(jù)阻塞,也不會(huì)影響其它任務(wù)的執(zhí)行。 1、多線程有什么用?一個(gè)可能在很多人看來很扯淡的一個(gè)問題:我會(huì)用多線程就好了,還管它有什么用?在我看來,這個(gè)回答更扯淡。所謂知其然知其所以然,會(huì)用只是知其然,為什么用才是知其所以然,只有達(dá)到知其然知其所以然的程度才可以說是把一個(gè)知識(shí)點(diǎn)...

    lpjustdoit 評(píng)論0 收藏0
  • 一起學(xué)并發(fā)編程 - synchronized詳解

    摘要:每個(gè)對(duì)象只有一個(gè)鎖與之相關(guān)聯(lián)。實(shí)現(xiàn)同步則是以系統(tǒng)開銷作為代價(jià),甚至可能造成死鎖,所以盡量避免濫用。這種機(jī)制確保了同一時(shí)刻該類實(shí)例,所有聲明為的函數(shù)中只有一個(gè)方法處于可執(zhí)行狀態(tài),從而有效避免了類成員變量訪問沖突。 synchronized是JAVA語(yǔ)言的一個(gè)關(guān)鍵字,使用 synchronized 來修飾方法或代碼塊的時(shí)候,能夠保證多個(gè)線程中最多只有一個(gè)線程執(zhí)行該段代碼 ... 概述 ...

    acrazing 評(píng)論0 收藏0
  • Java 線程核心技術(shù)梳理(附源碼)

    摘要:本文對(duì)多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對(duì)象及變量的并發(fā)訪問,線程間通信,的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。源碼采用構(gòu)建,多線程這部分源碼位于模塊中。通知可能等待該對(duì)象的對(duì)象鎖的其他線程。 本文對(duì)多線程基礎(chǔ)知識(shí)進(jìn)行梳理,主要包括多線程的基本使用,對(duì)象及變量的并發(fā)訪問,線程間通信,lock的使用,定時(shí)器,單例模式,以及線程狀態(tài)與線程組。 寫在前面 花了一周時(shí)...

    Winer 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<