首先我們了解到Java中的線程同步鎖可以是任意對象搀愧。
這里我們介紹synchronized的三種應用方式:
1.作用于實例方法家淤,當前實例加鎖佳晶,進入同步代碼前要獲得當前實例的鎖抄瓦;
2.作用于靜態(tài)方法坐求,當前類加鎖蚕泽,進去同步代碼前要獲得當前類對象的鎖;
3.作用于代碼塊桥嗤,這需要指定加鎖的對象须妻,對所給的指定對象加鎖,進入同步代碼前要獲得指定對象的鎖泛领。
這三種應用方式接下來分別介紹
synchronized修飾實例方法(普通方法)
??使用時荒吏,作用范圍為整個函數(shù),這里所謂的實例鎖就是調(diào)用該實例方法(不包括靜態(tài)方法)的對象渊鞋。不多BB绰更,上代碼:
【demo1】
public class SyncTest implements Runnable{
//共享資源變量
int count = 0;
@Override
public synchronized void run() {
for (int i = 0; i < 5; i++) {
increaseCount();
System.out.println(Thread.currentThread().getName()+":"+count++);
}
}
public static void main(String[] args) throws InterruptedException {
SyncTest syncTest1 = new SyncTest();
// SyncTest syncTest2 = new SyncTest();
Thread thread1 = new Thread(syncTest1,"thread1");
Thread thread2 = new Thread(syncTest1, "thread2");
thread1.start();
thread2.start();
}
}
/**
* 輸出結果
thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:5
thread2:6
thread2:7
thread2:8
thread2:9
*/
??代碼中開啟了兩個線程去操作一個變量(共享變量),count++是先讀取值,再寫回一個新值锡宋。我們想一下儡湾,如果第一個線程執(zhí)行這一過程中,第二個線程拿到寫回之前的count值执俩,做count++操作徐钠,那么這就造成了線程不安全。所以這里在run方法加上synchronized役首,獲取一個對象鎖尝丐,代碼中的實例鎖就是syncTest1了。
??同時我們從輸出結果看出:當一個線程正在訪問一個對象synchronized實例方法時宋税,別的線程是訪問不了的摊崭。一個對象一把鎖說的就是這個讼油,當線程獲取了該對象的鎖后杰赛,其他線程無法獲取該對象的鎖,當然就訪問不了該對象的synchronized方法矮台,但是乏屯!但是根时!但是!可以訪問該對象的其他未被synchronized修飾的方法辰晕。
??如果是一個線程 A 需要訪問實例對象 obj1 的 synchronized 方法 f1(當前對象鎖是obj1)蛤迎,另一個線程 B 需要訪問實例對象 obj2 的 synchronized 方法 f2(當前對象鎖是obj2),這樣是允許的含友,因為兩個實例對象鎖并不同相同替裆,此時如果兩個線程操作數(shù)據(jù)并非共享的,線程安全是有保障的窘问,遺憾的是如果兩個線程操作的是共享數(shù)據(jù)辆童,那么線程安全就有可能無法保證了。我們把上面代碼中的main方法中的注釋放開惠赫,表達這一線程不安全的現(xiàn)象
【demo2】
public class SyncTest implements Runnable{
//共享資源變量
int count = 0;
@Override
public synchronized void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+count++);
}
}
public static void main(String[] args) throws InterruptedException {
SyncTest syncTest1 = new SyncTest();
SyncTest syncTest2 = new SyncTest();
Thread thread1 = new Thread(syncTest1,"thread1");
Thread thread2 = new Thread(syncTest2, "thread2");
thread1.start();
thread2.start();
}
/**
* 輸出結果
thread1:0
thread2:0
thread1:1
thread2:1
thread1:2
thread2:2
thread1:3
thread2:3
thread1:4
thread2:4
*/
}
??我們從輸出結果來看把鉴,兩個線程可能同時拿到共享變量去做count++操作。上述操作中雖然我們的run方法還是使用synchronized修飾儿咱,但是我們new了兩個實例庭砍。這就意味存在了兩個不同的實例鎖,thread1和thread2分別進入了syncTest1和syncTest2的實例鎖混埠,當然保證不了線程安全怠缸。但是我們也有解決方案啦:如果synchronized修飾的是靜態(tài)方法呢?下面我們再介紹修飾靜態(tài)方法钳宪。
synchronized修飾靜態(tài)方法
??我們知道靜態(tài)方法是不屬于當前實例的凯旭,而是屬性類的,那么這個鎖就是類的class對象鎖使套,上述問題引刃而解罐呼,請看代碼:
【demo3】
public class SyncTest implements Runnable {
//共享資源變量
static int count = 0;
@Override
public synchronized void run() {
increaseCount();
}
private synchronized static void increaseCount() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
SyncTest syncTest1 = new SyncTest();
SyncTest syncTest2 = new SyncTest();
Thread thread1 = new Thread(syncTest1, "thread1");
Thread thread2 = new Thread(syncTest2, "thread2");
thread1.start();
thread2.start();
}
/**
* 輸出結果
thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:5
thread2:6
thread2:7
thread2:8
thread2:9
*/
}
??瞧瞧輸出結果,問題解決了沒侦高?同樣是new了兩個不同實例嫉柴,卻保持了線程同步。那是我們synchronizd修飾的是靜態(tài)方法奉呛,run方法中調(diào)用這個靜態(tài)方法计螺,再說一次 靜態(tài)方法不屬于當前實例,而是屬于類瞧壮。所以這個方案其實是用的一個把鎖登馒,而這個鎖就是這個類的class對象鎖。
??需要注意的是如果一個線程A調(diào)用一個實例對象的非static synchronized方法咆槽,而線程B需要調(diào)用這個實例對象所屬類的靜態(tài) synchronized方法陈轿,是允許的,不會發(fā)生互斥現(xiàn)象,因為訪問靜態(tài) synchronized 方法占用的鎖是當前類的class對象麦射,而訪問非靜態(tài) synchronized 方法占用的鎖是當前實例對象鎖(結合demo2蛾娶,demo3)。
synchronized修飾代碼塊
??首先這個使用時的場景是:在某些情況下潜秋,我們編寫的方法體可能比較大蛔琅,同時存在一些比較耗時的操作,而需要同步的代碼又只有一小部分峻呛,如果直接對整個方法進行同步操作罗售,可能會得不償失,此時我們可以使用同步代碼塊的方式對需要同步的代碼進行包裹钩述,這樣就無需對整個方法進行同步操作了莽囤。所以他的作用范圍為synchronizd(obj){}的這個大括號中
【demo4】
public class SyncTest implements Runnable {
//共享資源變量
static int count = 0;
private byte[] mBytes = new byte[0];
@Override
public synchronized void run() {
increaseCount();
}
private void increaseCount() {
//假設省略了其他操作的代碼。
//……………………
synchronized (this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
SyncTest syncTest1 = new SyncTest();
SyncTest syncTest2 = new SyncTest();
Thread thread1 = new Thread(syncTest1, "thread1");
Thread thread2 = new Thread(syncTest2, "thread2");
thread1.start();
thread2.start();
}
/**
* 輸出結果
thread1:0
thread2:0
thread1:1
thread2:2
thread2:4
thread1:3
thread2:5
thread1:5
thread2:7
thread1:6
*/
}
??從輸出結果看出切距,這個demo并沒有保證線程安全朽缎,因為我們指定鎖為this,指的就是調(diào)用這個方法的實例對象谜悟。這里我們new了兩個不同的實例對象syncTest1话肖,syncTest2,所以有兩個鎖葡幸,thread1與thread2分別進入自己傳入的對象鎖的線程執(zhí)行increaseCount方法最筒,做成線程不安全。如果把demo4的成員變量注釋放開蔚叨,并將mBytes傳入synchronized后面的括號中床蜘,也是線程不安全的結果。這里之所以加上mBytes這個對象是為了說明synchronized后面的括號中是可以指定任意對象充當鎖的蔑水,而零長度的byte數(shù)組對象創(chuàng)建起來將比任何對象都經(jīng)濟邢锯。當然,如果要使用這個經(jīng)濟實惠的鎖并保證線程安全搀别,那就不能new出多個不同實例對象出來啦丹擎。如果你非要想new兩個不同對象出來,又想保證線程同步的話歇父,那么synchronized后面的括號中可以填入SyncTest.class蒂培,表示這個類對象作為鎖,自然就能保證線程同步啦榜苫。使用方法為:
synchronized(xxxx.class){
//todo
}
總結
修飾普通方法 一個對象中的加鎖方法只允許一個線程訪問护戳。但要注意這種情況下鎖的是訪問該方法的實例對象, 如果多個線程不同對象訪問該方法垂睬,則無法保證同步媳荒。
修飾靜態(tài)方法 由于靜態(tài)方法是類方法抗悍, 所以這種情況下鎖的是包含這個方法的類,也就是類對象肺樟;這樣如果多個線程不同對象訪問該靜態(tài)方法,也是可以保證同步的逻淌。
修飾代碼塊 其中普通代碼塊 如Synchronized(obj) 這里的obj 可以為類中的一個屬性么伯、也可以是當前的對象,它的同步效果和修飾普通方法一樣卡儒;Synchronized方法 (obj.class)靜態(tài)代碼塊它的同步效果和修飾靜態(tài)方法類似田柔。
參考資料:
http://blog.csdn.net/javazejian/article/details/72828483
http://blog.csdn.net/luoweifu/article/details/46613015