1.Synchronized對(duì)象監(jiān)視器為Object時(shí)的使用
1)Synchronized修飾方法時(shí),持有當(dāng)前對(duì)象的鎖芹彬,當(dāng)有多個(gè)對(duì)象時(shí)县遣,不同的對(duì)象有不同對(duì)象的“監(jiān)視器”。
2)當(dāng)Synchronized修飾類中的一個(gè)方法熊榛,而該類中的另一個(gè)方法沒(méi)被Synchronized修飾時(shí),這種情況下當(dāng)A線程先調(diào)用被Synchronized修飾的方法時(shí)腕巡,A線程就先持有object對(duì)象的Lock鎖玄坦,但是B線程可以異步調(diào)用object對(duì)象中的非Synchronized類型的方法。
如果B線程在A線程調(diào)用Synchronized方法后再調(diào)用Synchronized方法绘沉,就需要等待煎楣,也就是同步。
2.臟讀:發(fā)生臟讀的情況是在讀取實(shí)例變量時(shí)车伞,此值已經(jīng)被其他線程更改過(guò)了择懂。
以下是一個(gè)臟讀情況:PublicVar類中setValue被synchronized修飾,但是getValue沒(méi)被synchronized修飾另玖,在setValue的方法中將username修改后使線程sleep5000ms困曙。這時(shí)username被更改但是userpassword沒(méi)有被更改,此時(shí)主線程中的getValue()讀取了數(shù)據(jù)讀到的是被更改的username “B” 和沒(méi)被更改的userpassword “AA”谦去。
public class PublicVar{
public String username = "A";
public String password = "AA";
synchronized public void setValue(String username,String password){
try {
this.username = username;
Thread.sleep(5000);
this.password = password;
System.out.println("setValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void getValue(){
System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password);
}
}
public class ThreadA extends Thread{
private PublicVar publicVar;
public ThreadA(publicVar publicVar){
super();
this.publicVar = publicVar;
}
@Override
public void run(){
super.run();
publicVar.setValue("B","BB");
}
}
public class Test{
public static void main(String[] args){
try{
PublicVar publicVarRef = new PublicVar()慷丽;
ThreadA thread = new ThreadA(publicVarRef);
thread.start();
thread.sleep(200);
publicVarRef.getValue();
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
該臟讀的解決方法就是在getValue()方法前也加上synchronized關(guān)鍵字鳄哭,這樣一來(lái)當(dāng)A線程調(diào)用anyObject對(duì)象加入synchronized關(guān)鍵字的X方法時(shí)要糊,A線程就獲得了X方法所在對(duì)象的鎖,所以其他線程必須等A線程執(zhí)行完畢才可以調(diào)用X方法妆丘,而B(niǎo)線程如果調(diào)用聲明了synchronized關(guān)鍵字的非X方法時(shí)锄俄,必須等A線程將X方法執(zhí)行完,也就是釋放對(duì)象鎖后才可以調(diào)用飘痛。這時(shí)A已經(jīng)執(zhí)行完一個(gè)完整的任務(wù)珊膜,也就是說(shuō)username和userpassword這兩個(gè)實(shí)例變量已經(jīng)同時(shí)被賦值,不存在臟讀的基本環(huán)境宣脉。
3.synchronized鎖重入:關(guān)鍵字synchronized擁有鎖重入的功能车柠,也就是在使用synchronized時(shí),當(dāng)一個(gè)線程得到一個(gè)對(duì)象鎖后塑猖,再次請(qǐng)求此對(duì)象鎖時(shí)是可以再次得到該對(duì)象的鎖的竹祷。這也證明在一個(gè)synchronized方法/塊的內(nèi)部調(diào)用本類的其他synchronized方法/塊時(shí),是永遠(yuǎn)可以得到鎖的羊苟。
子類是完全可以通過(guò)“可重入鎖”調(diào)用父類的同步方法的塑陵。
“可重入鎖”的概念是:自己可以再次獲取自己的內(nèi)部鎖。比如有1條線程獲得了某個(gè)對(duì)象的鎖蜡励,此時(shí)這個(gè)對(duì)象鎖還沒(méi)有釋放令花,當(dāng)其再次想要獲取這個(gè)對(duì)象的鎖的時(shí)候還是可以獲取的阻桅,如果鎖不可重入的話就會(huì)造成死鎖。
(底層原理是JVM的管程來(lái)實(shí)現(xiàn)詳細(xì)可以看另一篇博客)
4.出現(xiàn)異常兼都,鎖自動(dòng)釋放:當(dāng)一個(gè)線程執(zhí)行代碼出現(xiàn)異常時(shí)嫂沉,其所持有的鎖會(huì)自動(dòng)釋放。(底層也是管程來(lái)實(shí)現(xiàn):如果一個(gè)同步方法執(zhí)行期間拋出了異常扮碧,并且在方法內(nèi)部無(wú)法處理此異常趟章,那么這個(gè)同步方法所持有的管程將在異常拋到同步方法之外時(shí)自動(dòng)釋放)。
5.同步不具有繼承性:父類方法有synchronized關(guān)鍵字慎王,子類重寫(xiě)該方法不帶synchronized關(guān)鍵字蚓土,那么子類該方法就不是同步。
6.synchronized方法的缺點(diǎn)及synchronized代碼塊的優(yōu)點(diǎn):synchronized關(guān)鍵字修飾方法的缺點(diǎn)就在于赖淤,當(dāng)該方法需要長(zhǎng)時(shí)間執(zhí)行時(shí)蜀漆,另一個(gè)線程需要等待持有線程執(zhí)行完才能允許。 synchronized修飾代碼塊就可以解決這個(gè)缺點(diǎn)漫蛔,當(dāng)兩個(gè)并發(fā)線程訪問(wèn)同一個(gè)對(duì)象object中的synchronized(this)同步代碼塊時(shí)嗜愈,一段時(shí)間內(nèi)只有一個(gè)線程被執(zhí)行,另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼后才能執(zhí)行該代碼莽龟。也就是說(shuō)在方法內(nèi)同步代碼塊外的部分可以異步執(zhí)行蠕嫁。那么將長(zhǎng)時(shí)間執(zhí)行的一部分代碼置于同步代碼塊外就能異步執(zhí)行這一耗時(shí)較長(zhǎng)的代碼塊,從而縮短運(yùn)行時(shí)間毯盈。
7.synchronized代碼塊:關(guān)于synchronized(this)需要注意的是剃毒,當(dāng)一個(gè)線程訪問(wèn)object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對(duì)同一個(gè)object中所有其他synchronized(this)同步代碼塊的訪問(wèn)將被阻塞搂赋,這說(shuō)明synchronized使用的“對(duì)象監(jiān)視器”是一個(gè)赘阀。和synchronized方法一樣,synchronized(this)代碼也是鎖定當(dāng)前對(duì)象脑奠。
8.將任意對(duì)象作為對(duì)象監(jiān)視器:多個(gè)線程調(diào)用同一個(gè)對(duì)象中的不同名稱的synchronized同步方法或synchronized(this)同步代碼塊時(shí)基公,調(diào)用的效果就是按順序執(zhí)行,也就是同步的宋欺,阻塞的轰豆。
這說(shuō)明synchronized同步方法或synchronized(this)同步代碼塊分別有兩種作用
(1)synchronized同步方法
1)對(duì)其他synchronized同步方法或synchronized(this)同步代碼塊調(diào)用呈阻塞狀態(tài)。
2)同一時(shí)間只有一個(gè)線程可以執(zhí)行synchronized同步方法中的代碼齿诞。
(2)synchronized(this)同步代碼塊
1)對(duì)其他synchronized同步方法或synchronized(this)同步代碼塊調(diào)用呈阻塞狀態(tài)酸休。
2)同一時(shí)間只有一個(gè)線程可以執(zhí)行synchronized(this)同步方法中的代碼。
synchronized(非this對(duì)象x)同步代碼塊
1)在多個(gè)線程持有“對(duì)象監(jiān)視器”為同一對(duì)象的前提下祷杈,同一時(shí)間只有一個(gè)線程可以執(zhí)行synchronized(非this對(duì)象x)同步代碼塊斑司。
2)當(dāng)持有“對(duì)象監(jiān)視器”為同一個(gè)對(duì)象的前提下,同一時(shí)間只有一個(gè)線程可以執(zhí)行synchronized(非this對(duì)象x)同步代碼塊中的代碼但汞。
鎖非this對(duì)象具有一定的優(yōu)點(diǎn):如果在一個(gè)類中有很多個(gè)synchronized方法宿刮,這時(shí)雖然能實(shí)現(xiàn)同步互站,但會(huì)受到阻塞,所以影響運(yùn)行效率糙置;但如果使用同步代碼塊鎖非this對(duì)象云茸,則synchronized(非this)代碼塊中的程序與同步方法是異步的是目,不與其他鎖this同步方法爭(zhēng)搶this鎖谤饭,則可大大提高運(yùn)行效率。
9.下面是一個(gè)兩個(gè)synchronized同步方法之間的臟讀現(xiàn)象:main線程開(kāi)始執(zhí)行時(shí)Thread1先執(zhí)行并進(jìn)入if (list.getSize() < 1) 判斷語(yǔ)句中懊纳,并進(jìn)入睡眠揉抵,但是另一個(gè)線程可以異步也進(jìn)入if (list.getSize() < 1) 這個(gè)判斷語(yǔ)句中那么這兩個(gè)就都可以add(data)。
因?yàn)樗鼈兂钟械氖遣煌膶?duì)象監(jiān)視器:同步代碼塊放在非同步synchronized方法中進(jìn)行聲明嗤疯,并不能保證調(diào)用方法的線程的執(zhí)行同步/順序性冤今,也就是線程調(diào)用方法的順序是無(wú)序的,雖然在同步塊中執(zhí)行的順序是同步的茂缚,這樣極易出現(xiàn)臟讀問(wèn)題戏罢。
public class MyOneList{
private List list = new ArrayList();
synchronized public void add(String data){
list.add(data);
}
synchronized public int getSize(){
return list.size();
}
}
public class MyService {
public MyOneList addServiceMethod(MyOneList list,String data){
try {
if (list.getSize() < 1) {
Thread.sleep(2000);
list.add(data);
}
}catch(InterruptedException e){
e.printStackTrace();
}
return list;
}
}
public class MyThread1 extends Thread{
private MyOneList list;
public MyThread1(MyOneList list){
super();
this.list = list;
}
@Override
public void run(){
MyService msRef = new MyService();
msRef.addServiceMethod(list,"A");
}
}
public class MyThread2 extends Thread{
private MyOneList list;
public MyThread2(MyOneList list){
super();
this.list = list;
}
@Override
public void run(){
MyService msRef = new MyService();
msRef.addServiceMethod(list,"B");
}
}
public class run{
public static void main(String[] args) throws InterruptedException {
MyOneList list = new MyOneList();
MyThread1 thread1 = new MyThread1(list);
thread1.setName("A");
thread1.start();
MyThread2 thread2 = new MyThread2(list);
thread2.setName("B");
thread2.start();
Thread.sleep(6000);
System.out.println("listSize=" + list.getSize());
}
}
使用同步代碼塊可以避免上述臟讀現(xiàn)象,添加synchronized (list) 可以使兩個(gè)線程持有同一個(gè)對(duì)象監(jiān)視器:
public class MyService {
public MyOneList addServiceMethod(MyOneList list,String data){
try {
synchronized (list) {
if (list.getSize() < 1) {
Thread.sleep(2000);
}
}
}catch(InterruptedException e){
e.printStackTrace();
}
return list;
}
}
“synchronized (非list對(duì)象x) ”格式的寫(xiě)法是將x對(duì)象本身作為“對(duì)象監(jiān)視器”,這樣就可以得出以下3個(gè)結(jié)論:
1)當(dāng)多個(gè)線程同時(shí)執(zhí)行synchronized (x){}同步代碼塊時(shí)呈同步效果脚囊。
2)當(dāng)其他線程執(zhí)行x對(duì)象中synchronized同步方法時(shí)呈同步效果龟糕。
3)當(dāng)其他線程執(zhí)行x對(duì)象方法里面的synchronized (this)代碼塊時(shí)也是呈現(xiàn)同步效果。但需要注意:如果其他線程調(diào)用不加synchronized關(guān)鍵字的方法時(shí)悔耘,還是異步調(diào)用讲岁。