類鎖和對象鎖

我的理解:1撵割、sync修飾代碼塊時候用的都是對象鎖:synchronized (XXX)

2、當(dāng)修飾非靜態(tài)方法時候要看調(diào)用時候是否是同一個對象,及也是用的當(dāng)前對象鎖

3、當(dāng)修飾靜態(tài)方法時候為類鎖(可能是由于靜態(tài)方法是所有對象公有的)

如何驗證2和3的觀點:同一個類中兩個方法帶循環(huán)并循環(huán)中sleep()责循,用sync字段修飾其中一個,new一個類的對象開兩個線程調(diào)用兩個方法(一個靜態(tài)調(diào)用攀操,一個類對象調(diào)用)院仿,這兩個方法會不規(guī)律調(diào)用

一、synchronized關(guān)鍵字

synchronized關(guān)鍵字有如下兩種用法:

1崔赌、 在需要同步的方法的方法簽名中加入synchronized關(guān)鍵字意蛀。

synchronizedpublicvoidgetValue() {

System.out.println("getValue method thread name="+ Thread.currentThread().getName() +" username="+ username

+" password="+ password);

}

上面的代碼修飾的synchronized是非靜態(tài)方法耸别,如果修飾的是靜態(tài)方法(static)含義是完全不一樣的健芭。具體不一樣在哪里县钥,后面會詳細(xì)說清楚。

synchronizedstaticpublicvoidgetValue() {

System.out.println("getValue method thread name="+ Thread.currentThread().getName() +" username="+ username

+" password="+ password);

}

2慈迈、使用synchronized塊對需要進(jìn)行同步的代碼段進(jìn)行同步若贮。

publicvoidserviceMethod() {try{

synchronized (this) {

System.out.println("begin time="+ System.currentTimeMillis());

Thread.sleep(2000);

System.out.println("end? ? end="+ System.currentTimeMillis());

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

上面的代碼塊是synchronized (this)用法,還有synchronized (非this對象)以及synchronized (類.class)這兩種用法痒留,這些使用方式的含義也是有根本的區(qū)別的谴麦。我們先帶著這些問題繼續(xù)往下看。

二伸头、Java中的對象鎖和類鎖

小寶鴿似乎并沒有辦法用清晰簡短的語言來描述對象鎖和類鎖的概念匾效。即便能用簡單的語句概況,也會顯得抽象恤磷。猿友們耐心看完自然會明白面哼。

之前網(wǎng)上有找一些相關(guān)資料扫步,有篇博客是這樣描述的(看的是轉(zhuǎn)載的河胎,原創(chuàng)連接我也不知道):

一段synchronized的代碼被一個線程執(zhí)行之前游岳,他要先拿到執(zhí)行這段代碼的權(quán)限胚迫,

在Java里邊就是拿到某個同步對象的鎖(一個對象只有一把鎖);

如果這個時候同步對象的鎖被其他線程拿走了摩骨,他(這個線程)就只能等了(線程阻塞在鎖池等待隊列中)恼五。

取到鎖后灾馒,他就開始執(zhí)行同步代碼(被synchronized修飾的代碼)睬罗;

線程執(zhí)行完同步代碼后馬上就把鎖還給同步對象古涧,其他在鎖池中等待的某個線程就可以拿到鎖執(zhí)行同步代碼了羡滑。

這樣就保證了同步代碼在統(tǒng)一時刻只有一個線程在執(zhí)行。

上面提到鎖职祷,這里先引出鎖的概念有梆。先來看看下面這些啰嗦而必不可少的文字淳梦。

多線程的線程同步機(jī)制實際上是靠鎖的概念來控制的。

在Java程序運行時環(huán)境中陨囊,JVM需要對兩類線程共享的數(shù)據(jù)進(jìn)行協(xié)調(diào):

1)保存在堆中的實例變量

2)保存在方法區(qū)中的類變量

這兩類數(shù)據(jù)是被所有線程共享的。

(程序不需要協(xié)調(diào)保存在Java 棧當(dāng)中的數(shù)據(jù)压语。因為這些數(shù)據(jù)是屬于擁有該棧的線程所私有的。)

這里插播一下廣告:關(guān)于JVM內(nèi)存厕怜,如果想了解可以看看博主的另外一篇文章:

Java內(nèi)存管理:http://blog.csdn.net/u013142781/article/details/50830754

方法區(qū)(Method Area)與Java堆一樣琅捏,是各個線程共享的內(nèi)存區(qū)域柄延,它用于存儲已被虛擬機(jī)加載的類信息蜡坊、常量秕衙、靜態(tài)變量鹦牛、即時編譯器編譯后的代碼等數(shù)據(jù)曼追。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個邏輯部分礼殊,但是它卻有一個別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開來婚陪。

棧:在Java中,JVM中的棧記錄了線程的方法調(diào)用沽一。每個線程擁有一個棧。在某個線程的運行過程中攘残,如果有新的方法調(diào)用遗契,那么該線程對應(yīng)的棧就會增加一個存儲單元牍蜂,即幀(frame)。在frame中,保存有該方法調(diào)用的參數(shù)僵井、局部變量和返回地址。

堆是JVM中一塊可自由分配給對象的區(qū)域驻债。當(dāng)我們談?wù)摾厥?garbage collection)時,我們主要回收堆(heap)的空間。

Java的普通對象存活在堆中翩伪。與棧不同,堆的空間不會隨著方法調(diào)用結(jié)束而清空犁珠。因此,在某個方法中創(chuàng)建的對象炊昆,可以在方法調(diào)用結(jié)束之后洛搀,繼續(xù)存在于堆中卷要。這帶來的一個問題是棺榔,如果我們不斷的創(chuàng)建新的對象,內(nèi)存空間將最終消耗殆盡。

在java虛擬機(jī)中,每個對象和類在邏輯上都是和一個監(jiān)視器相關(guān)聯(lián)的。

對于對象來說序六,相關(guān)聯(lián)的監(jiān)視器保護(hù)對象的實例變量繁涂。

對于類來說椭懊,監(jiān)視器保護(hù)類的類變量盅抚。

(如果一個對象沒有實例變量哪自,或者一個類沒有變量丰包,相關(guān)聯(lián)的監(jiān)視器就什么也不監(jiān)視。)

為了實現(xiàn)監(jiān)視器的排他性監(jiān)視能力壤巷,java虛擬機(jī)為每一個對象和類都關(guān)聯(lián)一個鎖邑彪。代表任何時候只允許一個線程擁有的特權(quán)。線程訪問實例變量或者類變量不需鎖胧华。

但是如果線程獲取了鎖寄症,那么在它釋放這個鎖之前,就沒有其他線程可以獲取同樣數(shù)據(jù)的鎖了矩动。(鎖住一個對象就是獲取對象相關(guān)聯(lián)的監(jiān)視器)

類鎖實際上用對象鎖來實現(xiàn)有巧。當(dāng)虛擬機(jī)裝載一個class文件的時候,它就會創(chuàng)建一個java.lang.Class類的實例悲没。當(dāng)鎖住一個對象的時候篮迎,實際上鎖住的是那個類的Class對象。

一個線程可以多次對同一個對象上鎖檀训。對于每一個對象柑潦,java虛擬機(jī)維護(hù)一個加鎖計數(shù)器,線程每獲得一次該對象峻凫,計數(shù)器就加1渗鬼,每釋放一次,計數(shù)器就減 1荧琼,當(dāng)計數(shù)器值為0時譬胎,鎖就被完全釋放了差牛。

java編程人員不需要自己動手加鎖,對象鎖是java虛擬機(jī)內(nèi)部使用的堰乔。

在java程序中偏化,只需要使用synchronized塊或者synchronized方法就可以標(biāo)志一個監(jiān)視區(qū)域。當(dāng)每次進(jìn)入一個監(jiān)視區(qū)域時镐侯,java 虛擬機(jī)都會自動鎖上對象或者類侦讨。

三、synchronized關(guān)鍵字各種用法與實例

看完了”二苟翻、Java中的對象鎖和類鎖”韵卤,我們再來結(jié)合”一、synchronized關(guān)鍵字”里面提到的synchronized用法崇猫。

事實上沈条,synchronized修飾非靜態(tài)方法、同步代碼塊的synchronized (this)用法和synchronized (非this對象)的用法鎖的是對象诅炉,線程想要執(zhí)行對應(yīng)同步代碼蜡歹,需要獲得對象鎖。

synchronized修飾靜態(tài)方法以及同步代碼塊的synchronized (類.class)用法鎖的是類涕烧,線程想要執(zhí)行對應(yīng)同步代碼月而,需要獲得類鎖。

因此澈魄,事實上synchronized關(guān)鍵字可以細(xì)分為上面描述的五種用法景鼠。

本文的實例均來自于《Java多線程編程核心技術(shù)》這本書里面的例子。

1痹扇、我們先看看非線程安全實例(Run.java):

publicclassRun{publicstaticvoidmain(String[] args) {

HasSelfPrivateNum numRef =newHasSelfPrivateNum();

ThreadA athread =newThreadA(numRef);

athread.start();

ThreadB bthread =newThreadB(numRef);

bthread.start();

}

}classHasSelfPrivateNum{privateintnum =0;publicvoidaddI(String username) {try{if(username.equals("a")) {

num =100;

System.out.println("a set over!");

Thread.sleep(2000);

}else{

num =200;

System.out.println("b set over!");

}

System.out.println(username +" num="+ num);

}catch(InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();

}

}

}classThreadAextendsThread{privateHasSelfPrivateNum numRef;publicThreadA(HasSelfPrivateNum numRef) {super();this.numRef = numRef;

}

@Overridepublicvoidrun() {super.run();

numRef.addI("a");

}

}classThreadBextendsThread{privateHasSelfPrivateNum numRef;publicThreadB(HasSelfPrivateNum numRef) {super();this.numRef = numRef;

}

@Overridepublicvoidrun() {super.run();

numRef.addI("b");

}

}

運行結(jié)果為:

asetover!

bsetover!

bnum=200anum=200

修改HasSelfPrivateNum如下,方法用synchronized修飾如下:

class HasSelfPrivateNum {privateintnum =0;

synchronizedpublicvoidaddI(String username) {try{if(username.equals("a")) {

num =100;

System.out.println("a set over!");

Thread.sleep(2000);

}else{

num =200;

System.out.println("b set over!");

}

System.out.println(username +" num="+ num);

}catch(InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();

}

}

}

運行結(jié)果是線程安全的:

bsetover!

bnum=200asetover!

anum=100

實驗結(jié)論:兩個線程訪問同一個對象中的同步方法是一定是線程安全的溯香。本實現(xiàn)由于是同步訪問鲫构,所以先打印出a,然后打印出b

這里線程獲取的是HasSelfPrivateNum的對象實例的鎖——對象鎖玫坛。

2结笨、多個對象多個鎖

就上面的實例,我們將Run改成如下:

publicclassRun {publicstaticvoidmain(String[] args) {

HasSelfPrivateNum numRef1 =newHasSelfPrivateNum();

HasSelfPrivateNum numRef2 =newHasSelfPrivateNum();

ThreadA athread =newThreadA(numRef1);

athread.start();

ThreadB bthread =newThreadB(numRef2);

bthread.start();

}

}

運行結(jié)果為:

asetover!

bsetover!

bnum=200anum=200

這里插播一下:同步不具有繼承性

3湿镀、同步塊synchronized (this)

我們先看看代碼實例(Run.java)

publicclassRun{publicstaticvoidmain(String[] args) {

ObjectService service =newObjectService();

ThreadA a =newThreadA(service);

a.setName("a");

a.start();

ThreadB b =newThreadB(service);

b.setName("b");

b.start();

}

}classObjectService{publicvoidserviceMethod() {try{

synchronized (this) {

System.out.println("begin time="+ System.currentTimeMillis());

Thread.sleep(2000);

System.out.println("end? ? end="+ System.currentTimeMillis());

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}classThreadAextendsThread{privateObjectService service;publicThreadA(ObjectService service) {super();this.service = service;

}

@Overridepublicvoidrun() {super.run();

service.serviceMethod();

}

}classThreadBextendsThread{privateObjectService service;publicThreadB(ObjectService service) {super();this.service = service;

}

@Overridepublicvoidrun() {super.run();

service.serviceMethod();

}

}

運行結(jié)果:

begintime=1466148260341endend=1466148262342begintime=1466148262342endend=1466148264378

這樣也是同步的炕吸,線程獲取的是同步塊synchronized (this)括號()里面的對象實例的對象鎖,這里就是ObjectService實例對象的對象鎖了勉痴。

需要注意的是synchronized (){}的{}前后的代碼依舊是異步的

4赫模、synchronized (非this對象)

我們先看看代碼實例(Run.java)

publicclassRun{publicstaticvoidmain(String[] args) {

Service service =newService("xiaobaoge");

ThreadA a =newThreadA(service);

a.setName("A");

a.start();

ThreadB b =newThreadB(service);

b.setName("B");

b.start();

}

}classService{

String anyString =newString();publicService(String anyString){this.anyString = anyString;

}publicvoidsetUsernamePassword(String username, String password) {try{

synchronized (anyString) {

System.out.println("線程名稱為:"+ Thread.currentThread().getName()

+"在"+ System.currentTimeMillis() +"進(jìn)入同步塊");

Thread.sleep(3000);

System.out.println("線程名稱為:"+ Thread.currentThread().getName()

+"在"+ System.currentTimeMillis() +"離開同步塊");

}

}catch(InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();

}

}

}classThreadAextendsThread{privateService service;publicThreadA(Service service) {super();this.service = service;

}

@Overridepublicvoidrun() {

service.setUsernamePassword("a","aa");

}

}classThreadBextendsThread{privateService service;publicThreadB(Service service) {super();this.service = service;

}

@Overridepublicvoidrun() {

service.setUsernamePassword("b","bb");

}

}

不難看出,這里線程爭奪的是anyString的對象鎖蒸矛,兩個線程有競爭同一對象鎖的關(guān)系瀑罗,出現(xiàn)同步

現(xiàn)在有一個問題:一個類里面有兩個非靜態(tài)同步方法胸嘴,會有影響么?

答案是:如果對象實例A斩祭,線程1獲得了對象A的對象鎖劣像,那么其他線程就不能進(jìn)入需要獲得對象實例A的對象鎖才能訪問的同步代碼(包括同步方法和同步塊)。不理解可以細(xì)細(xì)品味一下摧玫!

5耳奕、靜態(tài)synchronized同步方法

我們直接看代碼實例:

publicclassRun{publicstaticvoidmain(String[] args) {

ThreadA a =newThreadA();

a.setName("A");

a.start();

ThreadB b =newThreadB();

b.setName("B");

b.start();

}

}classService{

synchronizedpublicstaticvoidprintA() {try{

System.out.println("線程名稱為:"+ Thread.currentThread().getName()

+"在"+ System.currentTimeMillis() +"進(jìn)入printA");

Thread.sleep(3000);

System.out.println("線程名稱為:"+ Thread.currentThread().getName()

+"在"+ System.currentTimeMillis() +"離開printA");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

synchronizedpublicstaticvoidprintB() {

System.out.println("線程名稱為:"+ Thread.currentThread().getName() +"在"+ System.currentTimeMillis() +"進(jìn)入printB");

System.out.println("線程名稱為:"+ Thread.currentThread().getName() +"在"+ System.currentTimeMillis() +"離開printB");

}

}classThreadAextendsThread{

@Overridepublicvoidrun() {

Service.printA();

}

}classThreadBextendsThread{

@Overridepublicvoidrun() {

Service.printB();

}

}

運行結(jié)果:

線程名稱為:A在1466149372909進(jìn)入printA

線程名稱為:A在1466149375920離開printA

線程名稱為:B在1466149375920進(jìn)入printB

線程名稱為:B在1466149375920離開printB

兩個線程在爭奪同一個類鎖,因此同步

6诬像、synchronized (class)

對上面Service類代碼修改成如下:

class Service {

public static void printA() {

synchronized (Service.class) {

try {

System.out.println("線程名稱為:"+ Thread.currentThread().getName()

+"在"+ System.currentTimeMillis() +"進(jìn)入printA");Thread.sleep(3000);System.out.println("線程名稱為:"+ Thread.currentThread().getName()

+"在"+ System.currentTimeMillis() +"離開printA");} catch (InterruptedException e) {

e.printStackTrace();}

}

}

public static void printB() {

synchronized (Service.class) {

System.out.println("線程名稱為:"+ Thread.currentThread().getName()

+"在"+ System.currentTimeMillis() +"進(jìn)入printB");System.out.println("線程名稱為:"+ Thread.currentThread().getName()

+"在"+ System.currentTimeMillis() +"離開printB");}

}

}運行結(jié)果:

線程名稱為:A在1466149372909進(jìn)入printA

線程名稱為:A在1466149375920離開printA

線程名稱為:B在1466149375920進(jìn)入printB

線程名稱為:B在1466149375920離開printB

兩個線程依舊在爭奪同一個類鎖屋群,因此同步

需要特別說明:對于同一個類A,線程1爭奪A對象實例的對象鎖颅停,線程2爭奪類A的類鎖谓晌,這兩者不存在競爭關(guān)系。也就說對象鎖和類鎖互補(bǔ)干預(yù)內(nèi)政

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末癞揉,一起剝皮案震驚了整個濱河市纸肉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌喊熟,老刑警劉巖柏肪,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芥牌,居然都是意外死亡烦味,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門壁拉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谬俄,“玉大人,你說我怎么就攤上這事弃理±B郏” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵痘昌,是天一觀的道長钥勋。 經(jīng)常有香客問我,道長辆苔,這世上最難降的妖魔是什么算灸? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮驻啤,結(jié)果婚禮上菲驴,老公的妹妹穿的比我還像新娘。我一直安慰自己街佑,他們只是感情好谢翎,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布捍靠。 她就那樣靜靜地躺著,像睡著了一般森逮。 火紅的嫁衣襯著肌膚如雪榨婆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天褒侧,我揣著相機(jī)與錄音良风,去河邊找鬼。 笑死闷供,一個胖子當(dāng)著我的面吹牛烟央,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播歪脏,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼疑俭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了婿失?” 一聲冷哼從身側(cè)響起钞艇,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎豪硅,沒想到半個月后哩照,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡懒浮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年飘弧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砚著。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡次伶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出稽穆,到底是詐尸還是另有隱情学少,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布秧骑,位于F島的核電站,受9級特大地震影響扣囊,放射性物質(zhì)發(fā)生泄漏乎折。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一侵歇、第九天 我趴在偏房一處隱蔽的房頂上張望骂澄。 院中可真熱鬧,春花似錦惕虑、人聲如沸坟冲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽健提。三九已至琳猫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間私痹,已是汗流浹背脐嫂。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留紊遵,地道東北人账千。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像暗膜,于是被迫代替她去往敵國和親匀奏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

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