我的理解: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)政