成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

線程間的同步與通信(1)——同步代碼塊Synchronized

Gu_Yan / 1894人閱讀

摘要:前言同步代碼塊是中最基礎(chǔ)的實(shí)現(xiàn)線程間的同步與通信的機(jī)制之一,本篇我們將對(duì)同步代碼塊以及監(jiān)視器鎖的概念進(jìn)行討論。離開同步代碼塊后,所獲得的鎖會(huì)被自動(dòng)釋放。

前言

同步代碼塊(Synchronized Block) 是java中最基礎(chǔ)的實(shí)現(xiàn)線程間的同步與通信的機(jī)制之一,本篇我們將對(duì)同步代碼塊以及監(jiān)視器鎖的概念進(jìn)行討論。

系列文章目錄

什么是同步代碼塊(Synchronized Block)

同步代碼塊簡(jiǎn)單來(lái)說(shuō)就是將一段代碼用一把給鎖起來(lái), 只有獲得了這把鎖的線程才訪問, 并且同一時(shí)刻, 只有一個(gè)線程能持有這把鎖, 這樣就保證了同一時(shí)刻只有一個(gè)線程能執(zhí)行被鎖住的代碼.

這里有兩個(gè)關(guān)鍵字需要注意: 一段代碼.

一段代碼

一般來(lái)說(shuō), 由 synchronized 鎖住的代碼都是拿{}括起來(lái)的代碼塊:

synchronized(this) {
    //由鎖保護(hù)的代碼
}

但值得注意的是, synchronized 也可以用來(lái)修飾一個(gè)方法, 則對(duì)應(yīng)的被鎖保護(hù)的一段代碼很自然就是整個(gè)方法體.

public class Foo {
    public synchronized void doSomething() {
        // 由鎖保護(hù)的代碼
    }
}

其實(shí)鎖這個(gè)東西說(shuō)起來(lái)很抽象, 你可以就把它想象成現(xiàn)實(shí)中的鎖, 所以它只不過是一塊令牌, 一把尚方寶劍, 它是木頭做的還是金屬做的并不重要, 你可以拿任何東西當(dāng)作鎖, 重要的是它代表的含義: 誰(shuí)持有它, 誰(shuí)就有獨(dú)立訪問臨界區(qū)(即上面所說(shuō)的一段代碼)的權(quán)利.

在java中, 我們可以拿一個(gè)對(duì)象當(dāng)作鎖.

這里引用<>中的一段話:

每個(gè)java對(duì)象都可以用做一個(gè)實(shí)現(xiàn)同步的鎖, 這些鎖被稱為內(nèi)置鎖(Intrinsic Lock)或者監(jiān)視器鎖(Monitor Lock). 線程在進(jìn)入同步代碼塊之前會(huì)自動(dòng)獲得鎖, 并且在退出同步代碼塊時(shí)自動(dòng)釋放鎖.

獲得內(nèi)置鎖的唯一途徑就是進(jìn)入由這個(gè)鎖保護(hù)的同步代碼塊或方法.

所以, synchronized 同步代碼塊的標(biāo)準(zhǔn)寫法應(yīng)該是:

synchronized(reference-to-lock) {
    //臨界區(qū)
}

其中, 括號(hào)里面的reference-to-lock就是鎖的引用, 它只要指向一個(gè)Java對(duì)象就行, 你可以自己隨便new一個(gè)不相關(guān)的對(duì)象, 將它作為鎖放進(jìn)去, 也可以像之前的例子一樣, 直接使用this, 代表使用當(dāng)前對(duì)象作為鎖.

有的同學(xué)就要問了, 我們前面說(shuō)可以用synchronized修飾一個(gè)方法, 并且也知道對(duì)應(yīng)的由鎖保護(hù)的代碼塊就是整個(gè)方法體, 但是, 它的鎖是什么呢?
要回答這個(gè)問題,首先要區(qū)分synchronized 所修飾的方法是否是靜態(tài)方法:

如果synchronized所修飾的是靜態(tài)方法, 則其所用的鎖為Class對(duì)象  
如果synchronized所修飾的是非靜態(tài)方法, 則其所用的鎖為方法調(diào)用所在的對(duì)象

當(dāng)使用synchronized 修飾非靜態(tài)方法時(shí), 以下兩種寫法是等價(jià)的:

//寫法1
public synchronized void doSomething() {
    // 由鎖保護(hù)的代碼
}

//寫法2
public void doSomething() {
    synchronized(this) {
        // 由鎖保護(hù)的代碼
    }
}
到底拿什么鎖住了同步代碼塊

同步代碼塊中最難理解的部分就是拿什么作為了鎖, 上面我們已經(jīng)提到了三個(gè) this, Class對(duì)象, 方法調(diào)用所在的對(duì)象, 并且我們也說(shuō)明了可以拿任何java對(duì)象作為鎖.

this方法調(diào)用所在的對(duì)象

這兩個(gè)其實(shí)是一個(gè)意思, 我們需要特別注意的是, 一個(gè)Class可以有多個(gè)實(shí)例(Instance), 每一個(gè)Instance都可以作為鎖, 不同Instance就是不同的鎖, 同一個(gè)Instance就是同一個(gè)鎖, this方法調(diào)用所在的對(duì)象 指代的都是調(diào)用這個(gè)同步代碼塊的對(duì)象.

這么說(shuō)可能比較抽象, 我們直接上例子: (以下例子轉(zhuǎn)載自博客Java中Synchronized的用法)

class SyncThread implements Runnable {
    private static int count;

    public SyncThread() {
        count = 0;
    }

    public  void run() {
        synchronized(this) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public static void main(String[] args) {
        SyncThread syncThread = new SyncThread();
        //線程1和線程2使用了SyncThread類的同一個(gè)對(duì)象實(shí)例
        //因此, 這兩個(gè)線程中的synchronized(this), 持有的是同一把鎖
        Thread thread1 = new Thread(syncThread, "SyncThread1");
        Thread thread2 = new Thread(syncThread, "SyncThread2");
        thread1.start();
        thread2.start();
    }
}

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

SyncThread1:0  
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9

這里兩個(gè)線程SyncThread1SyncThread2 持有同一個(gè)對(duì)象syncThread的鎖, 因此同一時(shí)刻, 只有一個(gè)線程能訪問同步代碼塊, 線程SyncThread2 只有等 SyncThread1 執(zhí)行完同步代碼塊后, SyncThread1線程自動(dòng)釋放了鎖, 隨后 SyncThread2才能獲取同一把鎖, 進(jìn)入同步代碼塊.

我們也可以修改一下main函數(shù), 讓兩個(gè)線程持有同一Class對(duì)象的不同實(shí)例的鎖:

public static void main(String[] args) {
    Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
    Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
    thread1.start();
    thread2.start();
}

上面這段等價(jià)于:

public static void main(String[] args) {
    SyncThread syncThread1 = new SyncThread();
    SyncThread syncThread2 = new SyncThread();
    Thread thread1 = new Thread(syncThread1, "SyncThread1");
    Thread thread2 = new Thread(syncThread2, "SyncThread2");
    thread1.start();
    thread2.start();
}

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

SyncThread1:0  
SyncThread2:1
SyncThread1:2
SyncThread2:3
SyncThread1:4
SyncThread2:5
SyncThread1:6
SyncThread2:7
SyncThread1:8
SyncThread2:9

可見, 兩個(gè)線程這次都能訪問同步代碼塊, 這是因?yàn)榫€程1執(zhí)行的是syncThread1對(duì)象的同步代碼塊, 線程2執(zhí)行的是syncThread2的同步代碼塊, 雖然這兩個(gè)同步代碼塊一樣, 但是他們?cè)诓煌膶?duì)象實(shí)例里面, 即雖然它們都用this作為鎖, 但是this指代的對(duì)象在這兩個(gè)線程中不是同一個(gè)對(duì)象, 兩個(gè)線程各自都能獲得鎖, 因此各自都能執(zhí)行這一段同步代碼塊.

這告訴我們, 當(dāng)一段代碼用同步代碼塊包起來(lái)的時(shí)候, 并不絕對(duì)意味著這段代碼同一時(shí)刻只能由一個(gè)線程訪問, 這種情況只發(fā)生在多個(gè)線程訪問的是同一個(gè)Instance, 也就是說(shuō), 多個(gè)線程請(qǐng)求的是同一把鎖.

再回顧我們上面兩個(gè)例子, 第一個(gè)例子中, 兩個(gè)線程使用的是同一個(gè)對(duì)象實(shí)例, 他們需要同一把對(duì)象鎖 syncThread,
第二個(gè)例子中, 兩個(gè)線程分別使用了一個(gè)對(duì)象實(shí)例, 他們分別請(qǐng)求的是自己訪問的對(duì)象實(shí)例的鎖syncThread1, syncThread2, 因此都能訪問同步代碼塊.

導(dǎo)致不同線程可以同時(shí)訪問同步代碼塊的最根本原因就是我們使用的是當(dāng)前實(shí)例對(duì)象鎖(this), 因?yàn)轭惖膶?shí)例可以有多個(gè), 這導(dǎo)致了同步代碼塊散布在類的多個(gè)實(shí)例中, 雖然同一個(gè)實(shí)例中的同步代碼塊只能由持有鎖的單個(gè)線程訪問(this對(duì)象鎖保護(hù)), 但是我們可以每個(gè)線程訪問自己的對(duì)象實(shí)例, 而每一個(gè)對(duì)象實(shí)例的同步代碼塊都是一致的, 這就間接導(dǎo)致了多個(gè)線程同時(shí)訪問了"同一個(gè)"同步代碼塊.

上面這種情況在某些條件下是沒有問題的, 例如同步代碼塊中不存在對(duì)靜態(tài)變量(共享的狀態(tài)量)的修改.

但是, 對(duì)于上面的例子, 這樣的情況明顯違背了我們加同步代碼塊的初衷.

要解決上面的情況, 一種可行的辦法就是像第一個(gè)例子一樣, 多個(gè)線程使用同一個(gè)對(duì)象實(shí)例, 例如在單例模式下, 本身就只有一個(gè)對(duì)象實(shí)例, 所以多個(gè)線程必將請(qǐng)求同一把鎖, 從而實(shí)現(xiàn)同步訪問.

另一種方法就是我們下面要講的: 使用Class鎖.

使用Class級(jí)別鎖

前面我們提到:

如果synchronized所修飾的是靜態(tài)方法, 則其所用的鎖為Class對(duì)象

這是因?yàn)殪o態(tài)方法是屬于類的而不屬于對(duì)象的, 因此synchronized修飾的靜態(tài)方法鎖定的是這個(gè)類的所有對(duì)象。我們來(lái)看下面一個(gè)例子(以下例子同樣轉(zhuǎn)載自博客Java中Synchronized的用法):

class SyncThread implements Runnable {
    private static int count;

    public SyncThread() {
        count = 0;
    }

    // synchronized 關(guān)鍵字加在一個(gè)靜態(tài)方法上
    public synchronized static void staticMethod() {
        for (int i = 0; i < 5; i ++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void run() {
        staticMethod();
    }

    public static void main(String[] args) {
        SyncThread syncThread1 = new SyncThread();
        SyncThread syncThread2 = new SyncThread();
        Thread thread1 = new Thread(syncThread1, "SyncThread1");
        Thread thread2 = new Thread(syncThread2, "SyncThread2");
        thread1.start();
        thread2.start();
    }
}

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

SyncThread1:0  
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9

可見, 靜態(tài)方法鎖定了類的所有對(duì)象, 用我們之前的話來(lái)說(shuō), 如果說(shuō)"因?yàn)轭惖膶?shí)例可以有多個(gè), 這導(dǎo)致了同步代碼塊散布在類的多個(gè)實(shí)例中", 那么類的靜態(tài)方法就是阻止同步代碼塊散布在類的實(shí)例中, 因?yàn)轭惖撵o態(tài)方法只屬于類本身.

其實(shí), 上面的例子的本質(zhì)就是拿Class對(duì)象作為鎖, 我們前面也提到了, 可以拿任何對(duì)象作為鎖, 如果我們直接拿類的Class對(duì)象作為鎖, 同樣可以保證所以線程請(qǐng)求的都是同一把鎖, 因?yàn)镃lass對(duì)象只有一個(gè).

類鎖實(shí)際上是通過對(duì)象鎖實(shí)現(xiàn)的,即類的 Class 對(duì)象鎖。每個(gè)類只有一個(gè) Class 對(duì)象,所以每個(gè)類只有一個(gè)類鎖。
class SyncThread implements Runnable {
    private static int count;

    public SyncThread() {
        count = 0;
    }

    public void run() {
        // 這里直接拿Class對(duì)象作為鎖
        synchronized(SyncThread.class) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        SyncThread syncThread1 = new SyncThread();
        SyncThread syncThread2 = new SyncThread();
        Thread thread1 = new Thread(syncThread1, "SyncThread1");
        Thread thread2 = new Thread(syncThread2, "SyncThread2");
        thread1.start();
        thread2.start();
    }
}

這樣所得到的結(jié)果與上面的類的靜態(tài)方法加鎖是一致的。

幾點(diǎn)補(bǔ)充

其實(shí)到這里, 重要的部分已經(jīng)講完了, 下面補(bǔ)充說(shuō)明幾點(diǎn):

(1) 當(dāng)一個(gè)線程訪問對(duì)象的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問該對(duì)象中的非synchronized(this)同步代碼塊。

這個(gè)結(jié)論是顯而易見的, 在沒有加鎖的情況下, 所有的線程都可以自由地訪問對(duì)象中的代碼, 而synchronized關(guān)鍵字只是限制了線程對(duì)于已經(jīng)加鎖的同步代碼塊的訪問, 并不會(huì)對(duì)其他代碼做限制.
這里也提示我們:

同步代碼塊應(yīng)該越短小越好

(2) 當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對(duì)object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。

這個(gè)結(jié)論也是顯而易見的, 因?yàn)閟ynchronized(this)拿的都是當(dāng)前對(duì)象的鎖, 如果一個(gè)線程已經(jīng)進(jìn)入了一個(gè)同步代碼塊, 說(shuō)明它已經(jīng)拿到了鎖, 而訪問同一個(gè)object中的其他同步代碼塊同樣需要當(dāng)前對(duì)象的鎖, 所以它們會(huì)被阻塞.

(3) synchronized關(guān)鍵字不能繼承。

對(duì)于父類中用synchronized 修飾的方法,子類在覆蓋該方法時(shí),默認(rèn)情況下不是同步的,必須顯式的使用 synchronized 關(guān)鍵字修飾才行, 當(dāng)然子類也可以直接調(diào)用父類的方法, 這樣就間接實(shí)現(xiàn)了同步.

(4) 在定義接口方法時(shí)不能使用synchronized關(guān)鍵字。
(5) 構(gòu)造方法不能使用synchronized關(guān)鍵字,但可以使用synchronized代碼塊來(lái)進(jìn)行同步。
(6) 離開同步代碼塊后,所獲得的鎖會(huì)被自動(dòng)釋放。

總結(jié)

synchronized關(guān)鍵字通過一把鎖住一段代碼, 使得線程只有在持有鎖的時(shí)候才能訪問這段代碼

任何java對(duì)象都可以作為這把鎖

可以在synchronized后面用()顯式的指定鎖. 也可以直接作用在方法上

作用于普通方法時(shí), 相當(dāng)于以this對(duì)象作為鎖, 此時(shí)同步代碼塊散布于類的所有實(shí)例中, 每一個(gè)實(shí)例的同步代碼塊的鎖 為該實(shí)例對(duì)象自身。

作用于靜態(tài)方法時(shí), 相當(dāng)于以Class對(duì)象作為鎖, 此時(shí)對(duì)象的所有實(shí)例只能爭(zhēng)搶同一把鎖。

內(nèi)置鎖的一個(gè)重要的特性是當(dāng)離開同步代碼塊之后, 會(huì)自動(dòng)釋放鎖,而其他的高級(jí)鎖(如ReentrantLock)需要顯式釋放鎖。

思考題

前面我們說(shuō)明了synchronized 的使用方法,但對(duì)一些底層的細(xì)節(jié)并不了解,如:

前面說(shuō)“獲得內(nèi)置鎖的唯一途徑就是進(jìn)入由這個(gè)鎖保護(hù)的同步代碼塊或方法.”, 這句話看上去很有道理,其實(shí)是廢話,同步代碼塊究竟是怎么獲得鎖的?

我們說(shuō),JAVA中任何對(duì)象都可以作為鎖,那么鎖信息是怎么被記錄和存儲(chǔ)的?

為什么代碼離開了同步代碼塊鎖就被釋放了,誰(shuí)釋放了鎖,怎樣叫釋放了鎖?

這些問題,我們后續(xù)的文章再研究。

(完)

查看更多系列文章:系列文章目錄

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

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

相關(guān)文章

  • 線程間的同步通信(3)——淺析synchronized的實(shí)現(xiàn)原理

    摘要:由此可見,自旋鎖和各有優(yōu)劣,他們分別適用于競(jìng)爭(zhēng)不多和競(jìng)爭(zhēng)激烈的場(chǎng)景中。每一個(gè)試圖進(jìn)入同步代碼塊的線程都會(huì)被封裝成對(duì)象,它們或在對(duì)象的中,或在中,等待成為對(duì)象的成為的對(duì)象即獲取了監(jiān)視器鎖。 前言 系列文章目錄 前面兩篇文章我們介紹了synchronized同步代碼塊以及wait和notify機(jī)制,大致知道了這些關(guān)鍵字和方法是干什么的,以及怎么用。 但是,知其然,并不知其所以然。 例如...

    keithxiaoy 評(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
  • java 多線程編程核心技術(shù) 3—線程通信

    摘要:在從返回前,線程與其他線程競(jìng)爭(zhēng)重新獲得鎖。就緒隊(duì)列存儲(chǔ)了將要獲得鎖的線程,阻塞隊(duì)列存儲(chǔ)了被阻塞的線程。當(dāng)線程呈狀態(tài),調(diào)用線程對(duì)象的方法會(huì)出現(xiàn)異常。在執(zhí)行同步代碼塊過程中,遇到異常而導(dǎo)致線程終止,鎖也會(huì)被釋放。 方法wait()的作用是使當(dāng)前執(zhí)行代碼的線程進(jìn)行等待,wait()方法是Object類的方法,該方法用來(lái)將當(dāng)前線程置入預(yù)執(zhí)行隊(duì)列中,并且在wait()所在的代碼行處停止執(zhí)行,直...

    Dogee 評(píng)論0 收藏0
  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷懽鞯臅r(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問題,就要把一系列知識(shí)都了解一遍,寫出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...

    lijy91 評(píng)論0 收藏0
  • 系列文章目錄

    摘要:為了避免一篇文章的篇幅過長(zhǎng),于是一些比較大的主題就都分成幾篇來(lái)講了,這篇文章是筆者所有文章的目錄,將會(huì)持續(xù)更新,以給大家一個(gè)查看系列文章的入口。 前言 大家好,筆者是今年才開始寫博客的,寫作的初衷主要是想記錄和分享自己的學(xué)習(xí)經(jīng)歷。因?yàn)閷懽鞯臅r(shí)候發(fā)現(xiàn),為了弄懂一個(gè)知識(shí),不得不先去了解另外一些知識(shí),這樣以來(lái),為了說(shuō)明一個(gè)問題,就要把一系列知識(shí)都了解一遍,寫出來(lái)的文章就特別長(zhǎng)。 為了避免一篇...

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

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

0條評(píng)論

閱讀需要支付1元查看
<