Java 中的鎖

1、什么是鎖

談到j(luò)ava中的鎖,在我的印象中,無(wú)非是synchronized和lock這兩個(gè)模糊的概念。大致知道他們的區(qū)別:synchronized是基于jvm的鎖機(jī)制,不需要自己釋放鎖;lock較前者更靈活,但是需要自己獲取和釋放鎖。兩者都是用于在多線程中保證線程安全。

2、鎖相關(guān)的知識(shí)

和鎖相關(guān)概念有這些:

  • 線程安全
  • 主內(nèi)存和工作內(nèi)存
  • 自旋鎖
  • 鎖消除和鎖粗化
  • volatile
  • CAS
  • 互斥同步

3、線程和進(jìn)程

3.1 進(jìn)程

進(jìn)程是os中資源分配和獨(dú)立運(yùn)行的基本單位,包含程序、數(shù)據(jù)和進(jìn)程控制塊(PCB)。

  • 程序用于描述進(jìn)程要完成的功能,是控制進(jìn)程執(zhí)行的指令集;
  • 數(shù)據(jù)集合是程序在執(zhí)行時(shí)所需要的數(shù)據(jù)和工作區(qū);
  • 程序控制塊(Program Control Block,簡(jiǎn)稱PCB),包含進(jìn)程的描述信息和控制信息,是進(jìn)程存在的唯一標(biāo)志。
    其兩個(gè)基本屬性是:
  • 進(jìn)程是一個(gè)可擁有資源的獨(dú)立單位;
  • 進(jìn)程同時(shí)又是一個(gè)可獨(dú)立調(diào)度和分派的基本單位。這兩個(gè)基本屬性,構(gòu)成了進(jìn)程并發(fā)執(zhí)行的基礎(chǔ)。

3.2 線程

線程被稱為輕量級(jí)進(jìn)程,使得每個(gè)進(jìn)程擁有多個(gè)線程。進(jìn)程的引用是為了似多個(gè)程序并發(fā)執(zhí)行,來(lái)提高資源利用率和系統(tǒng)吞吐量,但是由于進(jìn)程創(chuàng)建、撤銷和切換PCB的時(shí)候,會(huì)帶來(lái)相對(duì)大量的時(shí)空消耗,在這種場(chǎng)景下,線程便應(yīng)運(yùn)而生。所以線程解決的問(wèn)題就是既要保證多個(gè)程序并發(fā)執(zhí)行,又要盡量?jī)?yōu)化和減少時(shí)空消耗。線程擁有以下屬性:

  • 輕量實(shí)體,擁有極少的系統(tǒng)資源,來(lái)保證它能獨(dú)立運(yùn)行;
  • 獨(dú)立調(diào)度和分派的基本單位,由于線程很輕,所以調(diào)度和切換非常迅速而且開銷小;
  • 可并發(fā)執(zhí)行,這個(gè)沒(méi)什么可說(shuō)的,咱就是為了解決這個(gè)問(wèn)題的;
  • 共享進(jìn)程的資源,在同一個(gè)進(jìn)程中,各個(gè)線程可以共享該進(jìn)程的所有資源,這就是線程和進(jìn)程最核心的區(qū)別。

線程的生命周期:

線程的生命周期.png

3.3 進(jìn)程和線程的區(qū)別

  • 線程是程序執(zhí)行的最小單位,而進(jìn)程是操作系統(tǒng)分配資源的最小單位;
  • 一個(gè)進(jìn)程由一個(gè)或多個(gè)線程組成,線程是一個(gè)進(jìn)程中代碼的不同執(zhí)行路線;
  • 進(jìn)程之間相互獨(dú)立,但同一進(jìn)程下的各個(gè)線程之間共享程序的內(nèi)存空間(包括代碼段、數(shù)據(jù)集、堆等)及一些進(jìn)程級(jí)的資源(如打開文件和信號(hào)),某進(jìn)程內(nèi)的線程在其它進(jìn)程不可見;
  • 調(diào)度和切換:線程上下文切換比進(jìn)程上下文切換要快得多。

線程與進(jìn)程關(guān)系的示意圖:


進(jìn)程和線程的示例圖.png
單線程和多線程之間的關(guān)系.png

4、如何做到線程安全

多線程中,三個(gè)核心概念:原子性、可見性和順序性。

  • 原子性 顧名思義就是不可分割的操作,一個(gè)操作(有可能包含有多個(gè)子操作)要么全部執(zhí)行,要么全部都不執(zhí)行。
  • 可見性 就是多個(gè)線程訪問(wèn)同一個(gè)資源,其中一個(gè)線程修改共享的變量,其他的線程立即能看到,CPU從主內(nèi)存中讀數(shù)據(jù)的效率相對(duì)來(lái)說(shuō)不高,現(xiàn)在主流的計(jì)算機(jī)中,都有幾級(jí)緩存。每個(gè)線程讀取共享變量時(shí),都會(huì)將該變量加載進(jìn)其對(duì)應(yīng)CPU的高速緩存里,修改該變量后,CPU會(huì)立即更新該緩存,但并不一定會(huì)立即將其寫回主內(nèi)存(實(shí)際上寫回主內(nèi)存的時(shí)間不可預(yù)期)。此時(shí)其它線程(尤其是不在同一個(gè)CPU上執(zhí)行的線程)訪問(wèn)該變量時(shí),從主內(nèi)存中讀到的就是舊的數(shù)據(jù),而非第一個(gè)線程更新后的數(shù)據(jù)。
  • 順序性 就是程序執(zhí)行的順序是按照代碼的先后順序執(zhí)行的。

4.1 保證達(dá)到以上三個(gè)核心點(diǎn)就能保證線程安全。三種方案:

  • 互斥同步
    最常見的并發(fā)正確性保障手段,同步至多個(gè)線程并發(fā)訪問(wèn)共享數(shù)據(jù)時(shí),保證共享數(shù)據(jù)在同一個(gè)時(shí)刻只被一個(gè)線程使用。

  • 非阻塞同步
    互斥同步的主要問(wèn)題就是進(jìn)行線程的阻塞和喚醒所帶來(lái)的性能問(wèn)題,因此這個(gè)同步也被稱為阻塞同步,阻塞同步屬于一種悲觀的并發(fā)策略,認(rèn)為只要不去做正確的同步措施,就肯定會(huì)出問(wèn)題,無(wú)論共享的數(shù)據(jù)是否會(huì)出現(xiàn)競(jìng)爭(zhēng)。隨著硬件指令的發(fā)展,有了另外一個(gè)選擇,基于沖突檢測(cè)的樂(lè)觀并發(fā)策略,通俗的講就是先進(jìn)性操作,如果沒(méi)有其他線程爭(zhēng)用共享數(shù)據(jù),那操作就成功了,如果共享數(shù)據(jù)有爭(zhēng)用,產(chǎn)生了沖突,那就再進(jìn)行其他的補(bǔ)償措施(最常見的措施就是不斷的重試,直到成功為止),這種策略不需要把線程掛起,所以這種同步也被稱為非阻塞同步。

  • 無(wú)同步方案
    簡(jiǎn)單的理解就是沒(méi)有共享變量需要不同的線程去爭(zhēng)用,目前有兩種方案,一個(gè)是“可重入代碼”,這種代碼可以在執(zhí)行的任何時(shí)刻中斷它,轉(zhuǎn)而去執(zhí)行其他的另外一段代碼,當(dāng)控制權(quán)返回時(shí),程序繼續(xù)執(zhí)行,不會(huì)出現(xiàn)任何錯(cuò)誤。一個(gè)是“線程本地存儲(chǔ)”,如果變量要被多線程訪問(wèn),可以使用volatile關(guān)鍵字來(lái)聲明它為“易變的“,以此來(lái)實(shí)現(xiàn)多線程之間的可見性。同時(shí)也可以通過(guò)ThreadLocal來(lái)實(shí)現(xiàn)線程本地存儲(chǔ)的功能,一個(gè)線程的Thread對(duì)象中都有一個(gè)ThreadLocalMap對(duì)象,來(lái)實(shí)現(xiàn)KV數(shù)據(jù)的存儲(chǔ)。

5、 CAS 鎖

CAS就是Compare And Swap,比較和替換的意思,是設(shè)計(jì)并發(fā)算法時(shí)用到的一種技術(shù)。比較和替換是使用一個(gè)期望值和一個(gè)變量的當(dāng)前置比較,如果當(dāng)前變量值等于期望值,就使用一個(gè)新值替換當(dāng)前變量值。換言之CAS有三個(gè)操作數(shù):內(nèi)存值V、舊的預(yù)期值A(chǔ)、要修改的值B,當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值修改為B并返回true,否則什么都不做并返回false,是不是很好理解。
CAS其實(shí)就是用作原子操作的,java.util.concurrent包是完全建立在CAS之上的。當(dāng)前的處理器基本都支持CAS。

6、樂(lè)觀鎖和悲觀鎖

6.1 悲觀鎖(Pessimistic Lock)

具有強(qiáng)烈的獨(dú)占和排他特性。它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他[事務(wù)],以及來(lái)自外部系統(tǒng)的[事務(wù)處理]修改持保守態(tài)度,因此,在整個(gè)數(shù)據(jù)處理過(guò)程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫(kù)提供的鎖機(jī)制(也只有數(shù)據(jù)庫(kù)層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問(wèn)的[排他性],否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無(wú)法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))。

6.2 樂(lè)觀鎖( Optimistic Locking )

相對(duì)[悲觀鎖]而言,樂(lè)觀鎖機(jī)制采取了更加寬松的加鎖機(jī)制。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫(kù)的鎖機(jī)制實(shí)現(xiàn),以保證操作最大程度的獨(dú)占性。但隨之而來(lái)的就是數(shù)據(jù)庫(kù)性能的大量開銷,特別是對(duì)長(zhǎng)[事務(wù)]而言,這樣的開銷往往無(wú)法承受。而樂(lè)觀鎖機(jī)制在一定程度上解決了這個(gè)問(wèn)題。樂(lè)觀鎖,大多是基于數(shù)據(jù)版本( Version )記錄機(jī)制實(shí)現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),在基于數(shù)據(jù)庫(kù)表的版本解決方案中,一般是通過(guò)為數(shù)據(jù)庫(kù)表增加一個(gè) “version” 字段來(lái)實(shí)現(xiàn)。讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后更新時(shí),對(duì)此版本號(hào)加一。此時(shí),將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫(kù)表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫(kù)表當(dāng)前版本號(hào),則予以更新,否則認(rèn)為是過(guò)期數(shù)據(jù)。

簡(jiǎn)而言之:
悲觀鎖:假定會(huì)發(fā)生并發(fā)沖突,屏蔽一切可能違反數(shù)據(jù)完整性的操作。
樂(lè)觀鎖:假設(shè)不會(huì)發(fā)生并發(fā)沖突,只在提交操作時(shí)檢查是否違反數(shù)據(jù)完整性。樂(lè)觀鎖不能解決臟讀的問(wèn)題。

7、Java中的樂(lè)觀鎖和悲觀鎖

我們都知道,cpu是時(shí)分復(fù)用的,也就是把cpu的時(shí)間片,分配給不同的thread/process輪流執(zhí)行,時(shí)間片與時(shí)間片之間,需要進(jìn)行cpu切換,也就是會(huì)發(fā)生進(jìn)程的切換。切換涉及到清空寄存器,緩存數(shù)據(jù)。然后重新加載新的thread所需數(shù)據(jù)。當(dāng)一個(gè)線程被掛起時(shí),加入到阻塞隊(duì)列,在一定的時(shí)間或條件下,在通過(guò)notify(),notifyAll()喚醒回來(lái)。在某個(gè)資源不可用的時(shí)候,就將cpu讓出,把當(dāng)前等待線程切換為阻塞狀態(tài)。等到資源(比如一個(gè)共享數(shù)據(jù))可用了,那么就將線程喚醒,讓他進(jìn)入runnable狀態(tài)等待cpu調(diào)度。這就是典型的悲觀鎖的實(shí)現(xiàn)。獨(dú)占鎖是一種悲觀鎖,synchronized就是一種獨(dú)占鎖,它假設(shè)最壞的情況,并且只有在確保其它線程不會(huì)造成干擾的情況下執(zhí)行,會(huì)導(dǎo)致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。
但是,由于在進(jìn)程掛起和恢復(fù)執(zhí)行過(guò)程中存在著很大的開銷。當(dāng)一個(gè)線程正在等待鎖時(shí),它不能做任何事,所以悲觀鎖有很大的缺點(diǎn)。舉個(gè)例子,如果一個(gè)線程需要某個(gè)資源,但是這個(gè)資源的占用時(shí)間很短,當(dāng)線程第一次搶占這個(gè)資源時(shí),可能這個(gè)資源被占用,如果此時(shí)掛起這個(gè)線程,可能立刻就發(fā)現(xiàn)資源可用,然后又需要花費(fèi)很長(zhǎng)的時(shí)間重新?lián)屨兼i,時(shí)間代價(jià)就會(huì)非常的高。
所以就有了樂(lè)觀鎖的概念,他的核心思路就是,每次不加鎖而是假設(shè)沒(méi)有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止。在上面的例子中,某個(gè)線程可以不讓出cpu,而是一直while循環(huán),如果失敗就重試,直到成功為止。所以,當(dāng)數(shù)據(jù)爭(zhēng)用不嚴(yán)重時(shí),樂(lè)觀鎖效果更好。比如CAS就是一種樂(lè)觀鎖思想的應(yīng)用。
JDK1.5中引入了底層的支持,在int、long和對(duì)象的引用等類型上都公開了CAS的操作,并且JVM把它們編譯為底層硬件提供的最有效的方法,在運(yùn)行CAS的平臺(tái)上,運(yùn)行時(shí)把它們編譯為相應(yīng)的機(jī)器指令。在java.util.concurrent.atomic包下面的所有的原子變量類型中,比如AtomicInteger,都使用了這些底層的JVM支持為數(shù)字類型的引用類型提供一種高效的CAS操作。
在CAS操作中,會(huì)出現(xiàn)ABA問(wèn)題。就是如果V的值先由A變成B,再由B變成A,那么仍然認(rèn)為是發(fā)生了變化,并需要重新執(zhí)行算法中的步驟。有簡(jiǎn)單的解決方案:不是更新某個(gè)引用的值,而是更新兩個(gè)值,包括一個(gè)引用和一個(gè)版本號(hào),即使這個(gè)值由A變?yōu)锽,然后為變?yōu)锳,版本號(hào)也是不同的。AtomicStampedReference和AtomicMarkableReference支持在兩個(gè)變量上執(zhí)行原子的條件更新。AtomicStampedReference更新一個(gè)“對(duì)象-引用”二元組,通過(guò)在引用上加上“版本號(hào)”,從而避免ABA問(wèn)題,AtomicMarkableReference將更新一個(gè)“對(duì)象引用-布爾值”的二元組。

8、 synchronized

synchronized是Java中的關(guān)鍵字,是一種同步鎖。它修飾的對(duì)象有以下幾種:

  • 修飾一個(gè)代碼塊,被修飾的代碼塊稱為同步語(yǔ)句塊,其作用的范圍是大括號(hào){}括起來(lái)的代碼,作用的對(duì)象是調(diào)用這個(gè)代碼塊的對(duì)象;
  • 修飾一個(gè)方法,被修飾的方法稱為同步方法,其作用的范圍是整個(gè)方法,作用的對(duì)象是調(diào)用這個(gè)方法的對(duì)象;
  • 修改一個(gè)靜態(tài)的方法,其作用的范圍是整個(gè)靜態(tài)方法,作用的對(duì)象是這個(gè)類的所有對(duì)象;
  • 修改一個(gè)類,其作用的范圍是synchronized后面括號(hào)括起來(lái)的部分,作用主的對(duì)象是這個(gè)類的所有對(duì)象。

8.1 修飾方法中的代碼塊

package com.threadTest;

/**
 * Created by lipei on 2017/8/22.
 */
public class SyncThreadTest {

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


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 int getCount() {
        return count;
    }
}

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

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

當(dāng)兩個(gè)并發(fā)線程(thread1和thread2)訪問(wèn)同一個(gè)對(duì)象(syncThread)中的synchronized代碼塊時(shí),在同一時(shí)刻只能有一個(gè)線程得到執(zhí)行,另一個(gè)線程受阻塞,必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。Thread1和thread2是互斥的,因?yàn)樵趫?zhí)行synchronized代碼塊時(shí)會(huì)鎖定當(dāng)前的對(duì)象,只有執(zhí)行完該代碼塊才能釋放該對(duì)象鎖,下一個(gè)線程才能執(zhí)行并鎖定該對(duì)象。

package com.threadTest;

/**
 * Created by lipei on 2017/8/22.
 */
public class SyncThreadTest {

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


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 int getCount() {
        return count;
    }
}

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

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

這時(shí)創(chuàng)建了兩個(gè)SyncThread的對(duì)象syncThread1和syncThread2,線程thread1執(zhí)行的是syncThread1對(duì)象中的synchronized代碼(run),而線程thread2執(zhí)行的是syncThread2對(duì)象中的synchronized代碼(run);我們知道synchronized鎖定的是對(duì)象,這時(shí)會(huì)有兩把鎖分別鎖定syncThread1對(duì)象和syncThread2對(duì)象,而這兩把鎖是互不干擾的,不形成互斥,所以兩個(gè)線程可以同時(shí)執(zhí)行。

8.2 同一類中既有同步塊也有非同步塊

package com.threadTest;

/**
 * Created by lipei on 2017/8/22.
 */
public class SyncThreadTest {

    public static void main(String[] args) {
        Counter counter = new Counter();
        Thread thread1 = new Thread(counter, "A");
        Thread thread2 = new Thread(counter, "B");
        thread1.start();
        thread2.start();
    }
}


class Counter implements Runnable {
    private int count;

    public Counter() {
        count = 0;
    }

    public void countAdd() {
        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();
                }
            }
        }
    }

    //非synchronized代碼塊,未對(duì)count進(jìn)行讀寫操作,所以可以不用synchronized
    public void printCount() {
        for (int i = 0; i < 5; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + " count:" + count);
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void run() {
        String threadName = Thread.currentThread().getName();
        if (threadName.equals("A")) {
            countAdd();
        } else if (threadName.equals("B")) {
            printCount();
        }
    }
}

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

A:0
B count:1
A:1
B count:2
A:2
B count:3
A:3
B count:4
A:4
B count:5

上面代碼中countAdd是一個(gè)synchronized的,printCount是非synchronized的。從上面的結(jié)果中可以看出一個(gè)線程訪問(wèn)一個(gè)對(duì)象的synchronized代碼塊時(shí),別的線程可以訪問(wèn)該對(duì)象的非synchronized代碼塊而不受阻塞。

8.3 給對(duì)象加鎖

package com.threadTest;

/**
 * Created by lipei on 2017/8/22.
 */
public class SyncThreadTest {

    public static void main(String[] args) {
        Account account = new Account("zhang san", 10000.0f);
        AccountOperator accountOperator = new AccountOperator(account);

        final int THREAD_NUM = 5;
        Thread threads[] = new Thread[THREAD_NUM];
        for (int i = 0; i < THREAD_NUM; i ++) {
            threads[i] = new Thread(accountOperator, "Thread" + i);
            threads[i].start();
        }
    }
}


/**
 * 銀行賬戶類
 */
class Account {
    String name;
    float amount;

    public Account(String name, float amount) {
        this.name = name;
        this.amount = amount;
    }
    //存錢
    public  void deposit(float amt) {
        amount += amt;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //取錢
    public  void withdraw(float amt) {
        amount -= amt;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public float getBalance() {
        return amount;
    }
}

/**
 * 賬戶操作類
 */
class AccountOperator implements Runnable{
    private Account account;
    public AccountOperator(Account account) {
        this.account = account;
    }

    public void run() {
        synchronized (account) {
            account.deposit(500);
            account.withdraw(500);
            System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
        }
    }
}

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

Thread0:10000.0
Thread4:10000.0
Thread3:10000.0
Thread2:10000.0
Thread1:10000.0

在AccountOperator 類中的run方法里,我們用synchronized 給account對(duì)象加了鎖。這時(shí),當(dāng)一個(gè)線程訪問(wèn)account對(duì)象時(shí),其他試圖訪問(wèn)account對(duì)象的線程將會(huì)阻塞,直到該線程訪問(wèn)account對(duì)象結(jié)束。也就是說(shuō)誰(shuí)拿到那個(gè)鎖誰(shuí)就可以運(yùn)行它所控制的那段代碼。
當(dāng)有一個(gè)明確的對(duì)象作為鎖時(shí),就可以用類似下面這樣的方式寫程序。

public void method3(SomeObject obj)
{
   //obj 鎖定的對(duì)象
   synchronized(obj)
   {
      // todo
   }
}

當(dāng)沒(méi)有明確的對(duì)象作為鎖,只是想讓一段代碼同步時(shí),可以創(chuàng)建一個(gè)特殊的對(duì)象來(lái)充當(dāng)鎖:

class Test implements Runnable
{
   private byte[] lock = new byte[0];  // 特殊的instance變量
   public void method()
   {
      synchronized(lock) {
         // todo 同步代碼塊
      }
   }

   public void run() {

   }
}

說(shuō)明:零長(zhǎng)度的byte數(shù)組對(duì)象創(chuàng)建起來(lái)將比任何對(duì)象都經(jīng)濟(jì)――查看編譯后的字節(jié)碼:生成零長(zhǎng)度的byte[]對(duì)象只需3條操作碼,而Object lock = new Object()則需要7行操作碼。

8.4 給靜態(tài)方法加鎖

修飾一個(gè)靜態(tài)的方法

Synchronized也可修飾一個(gè)靜態(tài)方法,用法如下:

public synchronized static void method() {
   // todo
}

我們知道靜態(tài)方法是屬于類的而不屬于對(duì)象的。同樣的,synchronized修飾的靜態(tài)方法鎖定的是這個(gè)類的所有對(duì)象。

8.5 給類加鎖

Synchronized還可作用于一個(gè)類,用法如下:

class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}
/**
 * 同步線程
 */
class SyncThread implements Runnable {
   private static int count;

   public SyncThread() {
      count = 0;
   }

   public static void method() {
      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 synchronized void run() {
      method();
   }
}

8.6 總結(jié)

  • 無(wú)論synchronized關(guān)鍵字加在方法上還是對(duì)象上,如果它作用的對(duì)象是非靜態(tài)的,則它取得的鎖是對(duì)象;如果synchronized作用的對(duì)象是一個(gè)靜態(tài)方法或一個(gè)類,則它取得的鎖是對(duì)類,該類所有的對(duì)象同一把鎖。
  • 每個(gè)對(duì)象只有一個(gè)鎖(lock)與之相關(guān)聯(lián),誰(shuí)拿到這個(gè)鎖誰(shuí)就可以運(yùn)行它所控制的那段代碼。
  • 實(shí)現(xiàn)同步是要很大的系統(tǒng)開銷作為代價(jià)的,甚至可能造成死鎖,所以盡量避免無(wú)謂的同步控制。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,013評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,146評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,534評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,767評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,318評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,074評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,258評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,486評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評(píng)論 1 290
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,993評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,234評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容