多線程并發(fā)訪問同步問題磕潮。
java中每個(gè)對象編譯后的字節(jié)碼中翠胰,有個(gè)頭部。
頭部存儲(chǔ)著hashcode自脯,對象垃圾回收的年齡之景,還有就是內(nèi)部鎖信息。
線程間互斥同步就是使用synchronized實(shí)現(xiàn)的膏潮,synchronized關(guān)鍵字锻狗,在編譯后,會(huì)在同步塊的前后焕参,生成monitor enter和monitor exit字節(jié)碼指令轻纪。關(guān)鍵字鎖住的是對象的鎖。
synchronized關(guān)鍵字加鎖的過程是怎樣叠纷?
- java代碼碰到synchronized關(guān)鍵字刻帚,在編譯后,會(huì)在同步塊的前后涩嚣,生成monitor enter和monitor exit字節(jié)碼指令崇众。
- 線程碰到monitor enter指令后,會(huì)先判斷航厚,這個(gè)對象的鎖有沒有被別的線程占用顷歌,如果沒有,就占用鎖幔睬,并在鎖的計(jì)數(shù)器上+1(鎖是可以被同一個(gè)線程重入的眯漩,所以不會(huì)存在自己死鎖的情況)。
- 如果發(fā)現(xiàn)對象的鎖已經(jīng)被占用麻顶,線程就阻塞等待赦抖。
- 當(dāng)拿到鎖的線程執(zhí)行到monitor exit時(shí)舱卡,鎖的計(jì)數(shù)器-1,當(dāng)對象的鎖計(jì)數(shù)器為0時(shí)摹芙,表示對象的鎖被釋放了灼狰。別的線程可以去拿到鎖,進(jìn)入同步代碼塊了浮禾。
java的線程使用synchronized關(guān)鍵字同步相較于,其他的一些操作份汗,是很重的耗性能的操作盈电。因?yàn)樵诨コ獾却龁拘训牟僮髦校琷ava線程底層是要跟操作系統(tǒng)內(nèi)核線程去映射的杯活。所以開發(fā)人員在使用的時(shí)候應(yīng)該特別小心哈匆帚。
因?yàn)楹男阅芪兀砸灿辛讼鄳?yīng)的優(yōu)化的鎖的概念嚎幸。要根據(jù)場景去使用設(shè)置寄猩。
對象的鎖有重度鎖,輕量級鎖替废,偏向鎖(大概的實(shí)現(xiàn)思路椎镣,就跟數(shù)據(jù)庫的樂觀鎖是一個(gè)概念)兽赁。
synchronized關(guān)鍵字可以作用在哪些地方,分別的作用范圍是怎樣剪况?
代碼塊蒲跨,方法或悲,靜態(tài)方法堪唐,類
-
作用在代碼塊時(shí):
結(jié)論:synchronized代碼塊只會(huì)影響同一對象(因?yàn)殒i的是對象)的所有synchronized代碼塊的同步訪問淮菠,不影響不同對象的同步訪問荤堪,不影響同一對象的非synchronized代碼塊的同步訪問。 -
作用在方法
結(jié)論:跟作用在代碼塊是一樣拥知。
public class ThreadTest implements Runnable{
private static int count = 0;
private synchronized void increase() {
for (int i = 0; i < 3; i++) {
try {
System.out.println(Thread.currentThread().getName() + " :" + count++);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
increase();
}
public static void main(String[] args) {
//同一個(gè)對象
ThreadTest threadTest = new ThreadTest();
Thread t1 = new Thread(threadTest, "t1");
Thread t2 = new Thread(threadTest, "t2");
t1.start();
t2.start();
}
}
運(yùn)行結(jié)果
能看出來是互斥同步的,線程2在線程1執(zhí)行結(jié)束肮塞,才開始執(zhí)行。
如果不同的線程作用在不同的對象上:
public static void main(String[] args) {
//不同線程作用在不同的對象上
Thread t1 = new Thread(new ThreadTest(), "t1");
Thread t2 = new Thread(new ThreadTest(), "t2");
t1.start();
t2.start();
}
運(yùn)行結(jié)果:
能看出來猜欺,線程間是沒有約束的替梨。
然而副瀑,synchronized可以修飾方法恋谭,但它不屬于方法的一部分,因此狈孔,synchronized關(guān)鍵字不能被繼承材义。如果父類方法使用synchronized關(guān)鍵字,而子類中覆蓋了該方法油挥,則子類這個(gè)方法默認(rèn)是不同步的深寥,必須顯式地加上synchronized關(guān)鍵字才會(huì)同步。但是则酝,若在子類中調(diào)用父類相應(yīng)的同步方法闰集,則子類的方法也就相當(dāng)于同步了。
- 作用在靜態(tài)方法上
public class ThreadTest implements Runnable{
private static int count = 0;
private synchronized static void increase() {
for (int i = 0; i < 3; i++) {
try {
System.out.println(Thread.currentThread().getName() + " :" + count++);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
increase();
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadTest(), "t1");
Thread t2 = new Thread(new ThreadTest(), "t2");
t1.start();
t2.start();
}
}
兩個(gè)線程作用在不同的對象上妥泉,運(yùn)行結(jié)果也是互斥同步的。
結(jié)論:跟作用在方法上不同蝇率,synchronized修飾靜態(tài)方法時(shí)本慕,作用范圍是整個(gè)靜態(tài)方法,作用的對象是這個(gè)類的所有對象监氢。這是因?yàn)殪o態(tài)方法是屬于類的而不屬于對象藤违,synchronized的內(nèi)部鎖鎖定這個(gè)類所有對象。
- 作用在類上
public class ThreadTest implements Runnable{
private static int count = 0;
private void increase() {
synchronized (ThreadTest.class) {
for (int i = 0; i < 3; i++) {
try {
System.out.println(Thread.currentThread().getName() + " :" + count++);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
public void run() {
increase();
}
public static void main(String[] args) {
Thread t1 = new Thread(new ThreadTest(), "t1");
Thread t2 = new Thread(new ThreadTest(), "t2");
t1.start();
t2.start();
}
}
結(jié)論:作用范圍是synchronized后面{}內(nèi)的部分议街,作用的對象也是這個(gè)類的所有對象(跟作用在靜態(tài)方法上一樣)特漩。
tips:
只要明白synchronized關(guān)鍵字是通過對象的內(nèi)部鎖來實(shí)現(xiàn)同步的,再針對具體情況具體分析(靜態(tài)方法涂身、方法搓蚪、類、代碼塊)鳄炉,很快便能搞明白synchronized關(guān)鍵字的含義和用法。
《深入理解java虛擬機(jī)》 周志明
參考文章:https://juejin.im/entry/58070aabd20309006863772c