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ū)別。
線程的生命周期:
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)系的示意圖:
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ú)謂的同步控制。