前言
最近在看并發(fā)編程藝術(shù)這本書咙崎,對看書的一些筆記及個人工作中的總結(jié)弟跑。
在多線程并發(fā)編程中synchronized一直是元老級角色廊蜒,很多人都會稱呼它為重量級鎖。但是在jdk1.6各種優(yōu)化之后胯盯,有些情況它并不那么重了懈费。
synchronized可以在任意對象及方法上加鎖,而加鎖的這段代碼稱為"互斥區(qū)"或"臨界區(qū)"
對象鎖
/**
* 對象鎖
*/
public class SynchronizedTest1 extends Thread {
private int count = 5;
// synchronized加鎖
public synchronized void run() {
count--;
System.out.println(this.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
SynchronizedTest1 myThread = new SynchronizedTest1();
Thread t1 = new Thread(myThread, "t1");
Thread t2 = new Thread(myThread, "t2");
Thread t3 = new Thread(myThread, "t3");
Thread t4 = new Thread(myThread, "t4");
Thread t5 = new Thread(myThread, "t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
結(jié)果:
t1 count = 4
t5 count = 3
t4 count = 2
t3 count = 1
t2 count = 0
總結(jié):
當(dāng)多個線程訪問SynchronizedTest1的run方法時博脑,以排隊的方式進(jìn)行處理(這里排隊是按照CPU分配的先后順序而定的)憎乙,一個線程想要執(zhí)行synchronized修飾的方法里的代碼:首先是嘗試獲得鎖,如果拿到鎖叉趣,執(zhí)行synchronized代碼體內(nèi)容泞边,拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖疗杉,直到拿到為止阵谚,而且是多個線程同時去競爭這把鎖。(也就是會有鎖競爭的問題)烟具。
弊處:就是導(dǎo)致cpu的使用頻率過高梢什,更嚴(yán)重的情況導(dǎo)致宕機(jī),我們應(yīng)該在開發(fā)中避免多個線程搶一把鎖的問題朝聋。
多個線程多個鎖
public class SynchronizedTest2 {
private int num = 0;
public synchronized void printNum(String tag) {
try {
if (tag.equals("a")) {
num = 100;
System.out.println("tag a, set num over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("tag b, set num over!");
}
System.out.println("tag " + tag + ", num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//注意觀察run方法輸出順序
public static void main(String[] args) {
//倆個不同的對象,所以執(zhí)行printNum方法的時候就是二把鎖嗡午,一個對象一把鎖,所以二者互相不影響
final SynchronizedTest2 test1 = new SynchronizedTest2();
final SynchronizedTest2 test2 = new SynchronizedTest2();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
test1.printNum("a");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
test2.printNum("b");
}
});
t1.start();
t2.start();
}
}
結(jié)果:
tag a, set num over!
tag b, set num over!
tag b, num = 200
tag a, num = 100
總結(jié):
倆個不同的對象,所以執(zhí)行printNum方法的時候就是二把鎖玖翅,一個對象一把鎖翼馆,所以二者互相不影響。
類級鎖
synchronized修飾靜態(tài)方法
public class SynchronizedTest3 {
private static int num = 0;
//synchronized修飾靜態(tài)關(guān)鍵字
public static synchronized void printNum(String tag) {
try {
if (tag.equals("a")) {
num = 100;
System.out.println("tag a, set num over!");
Thread.sleep(2500);
} else {
num = 200;
System.out.println("tag b, set num over!");
}
System.out.println("tag " + tag + ", num = " + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 注意觀察run方法輸出順序
public static void main(String[] args) {
final SynchronizedTest3 m1 = new SynchronizedTest3();
final SynchronizedTest3 m2 = new SynchronizedTest3();
Thread t1 = new Thread(() -> m1.printNum("a"));
Thread t2 = new Thread(() -> m2.printNum("b"));
t1.start();
t2.start();
}
}
tag a, set num over!
tag a, num = 100
tag b, set num over!
tag b, num = 200
結(jié)果:
synchronized修飾的靜態(tài)方法的鎖金度,表示鎖定.class類应媚,類一級別的鎖(獨(dú)占.class類)。
實(shí)際工作中為了降低鎖的力度猜极,synchronized修飾代碼塊中姜,從而達(dá)到更好的性能。鎖是synchronized括號里配置的對象跟伏。
同步代碼塊
//使用synchronized修飾代碼塊丢胚,
public class SynchronizedTest4 {
private Object obj = new Object();
public void test() throws Exception{
System.out.println(Thread.currentThread().getName()+":1111");
Thread.sleep(10000);
synchronized (obj){
System.out.println(Thread.currentThread().getName()+":22222");
}
System.out.println(Thread.currentThread().getName()+":33333");
}
public static void main(String[] args) {
final SynchronizedTest4 test4 = new SynchronizedTest4();
new Thread(() -> {
try {
test4.test();
} catch (Exception e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() -> {
try {
test4.test();
} catch (Exception e) {
e.printStackTrace();
}
},"t2").start();
}
}
結(jié)果:
t1:1111
t2:1111
t1:22222
t2:22222
t2:33333
t1:33333
總結(jié):
java中的每一個對象都可以作為鎖:
- 對于普通同步方法,鎖的是當(dāng)前實(shí)列對象受扳。
- 對于靜態(tài)同步方法携龟,鎖的是當(dāng)前類的Class對象。
- 對于同步方法塊勘高,鎖是Synchonized括號里配置的對象峡蟋。
當(dāng)一個線程試圖訪問同步代碼塊時,它首先必須要得到鎖华望,退出或拋出異常時必須釋放鎖蕊蝗。
從JVM規(guī)范中可以看到Synchonized在JVM里的實(shí)現(xiàn)原理,JVM基于進(jìn)入和退出Monitor對象來實(shí)現(xiàn)方法同步和代碼塊同步赖舟,但兩者的實(shí)現(xiàn)細(xì)節(jié)不一樣蓬戚。代碼塊同步是使用monitorenter(監(jiān)視器鎖)和monitorexit指令實(shí)現(xiàn)的,而方法同步是使用另外一種方式實(shí)現(xiàn)的宾抓,同步方法中依靠方法修飾符上的 ACC_SYNCHRONIZED 實(shí)現(xiàn)子漩。細(xì)節(jié)在JVM規(guī)范里并沒有詳細(xì)說明。但是石洗,方法的同步同樣可以使用這兩個指令來實(shí)現(xiàn)痛单。
monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法結(jié)束處和異常處劲腿,JVM要保證每個monitorenter必須有對應(yīng)的monitorexit與之配對旭绒。任何對象都有一個monitor與之關(guān)聯(lián),當(dāng)且一個monitor被持有后焦人,它將處于鎖定狀態(tài)挥吵。線程執(zhí)行到monitorenter指令時,將會嘗試獲取對象所對應(yīng)的monitor的所有權(quán)花椭,即嘗試獲得對象的鎖忽匈。