Synchronized關(guān)鍵字一直是工作和面試中的重點(diǎn)。這篇文章準(zhǔn)備徹徹底底的從基礎(chǔ)使用到原理缺陷等各個(gè)方面來(lái)一個(gè)分析,這篇文章由于篇幅比較長(zhǎng)猾封,但是如果你有時(shí)間和耐心,相信會(huì)有一個(gè)比較大的收獲浊伙,所以,學(xué)習(xí)請(qǐng)慢慢來(lái)长捧。這篇文章主要從以下幾個(gè)方面進(jìn)行分析講解.
1嚣鄙、Synchronized關(guān)鍵字的簡(jiǎn)介,主要是為什么要使用Synchronized關(guān)鍵字唆姐,極其作用地位拗慨。
2、Synchronized關(guān)鍵字的使用奉芦,主要是從對(duì)象鎖和類鎖兩個(gè)角度赵抢。
3、Synchronized關(guān)鍵字的使用注意事項(xiàng)声功。分析了6種常見(jiàn)的使用情況烦却。
4、Synchronized關(guān)鍵字的兩個(gè)性質(zhì)先巴,主要是可重入性和不可中斷性其爵。
5冒冬、Synchronized關(guān)鍵字的底層原理。
6摩渺、Synchronized關(guān)鍵字的常見(jiàn)缺陷简烤。
以上我們主要是從這7個(gè)角度來(lái)分析Synchronized關(guān)鍵字,每一個(gè)角度說(shuō)實(shí)話都能單獨(dú)拿出來(lái)作為一篇文章來(lái)分析摇幻。但是由于考慮到文章的連貫性横侦,所以綜合在了一起,循序漸進(jìn)绰姻。下面我們就帶著這些問(wèn)題開(kāi)始今天的文章枉侧。
一、簡(jiǎn)介
Synchronized一句話來(lái)解釋其作用就是:能夠保證同一時(shí)刻最多只有一個(gè)線程執(zhí)行該段代碼狂芋,以達(dá)到并發(fā)安全的效果榨馁。也就是說(shuō)Synchronized就好比是一把鎖,某個(gè)線程把資源鎖住了之后帜矾,別人就不能使用了翼虫,只有當(dāng)這個(gè)線程用完了別人才能用。
對(duì)于Synchronized關(guān)鍵字來(lái)說(shuō)屡萤,它是并發(fā)編程中一個(gè)元老級(jí)角色蛙讥,也就是說(shuō)你只要學(xué)習(xí)并發(fā)編程,就必須要學(xué)習(xí)Synchronized關(guān)鍵字灭衷。由此可見(jiàn)其地位。
說(shuō)了這么多旁涤,好像我們還沒(méi)體驗(yàn)過(guò)它的威力翔曲。我們就直接舉個(gè)例子,來(lái)分析一下劈愚。
public class SynTest01 implements Runnable{
static int a=0;
public static void main(String[] args)
throws InterruptedException {
SynTest01 syn= new SynTest01();
Thread thread1 = new Thread(syn);
Thread thread2 = new Thread(syn);
thread1.start();thread1.join();
thread2.start();thread2.join();
System.out.println(a);
}
@Override
public void run() {
for(int i=0;i<1000;i++) {
a++;
}
}
}
上面代碼要完成的功能就是瞳遍,thread1對(duì)a進(jìn)行增加,一直到1000菌羽,thread2再對(duì)a進(jìn)行增加掠械,一直到2000。不過(guò)如果我們運(yùn)行過(guò)之后我們就會(huì)發(fā)現(xiàn)注祖,最后的輸出值總是小于2000猾蒂,這是為什么呢?
這是因?yàn)槲覀冊(cè)趫?zhí)行a++的時(shí)候其實(shí)包含了以下三個(gè)操作:
(1)線程1讀取a
(2)線程1將a加1
(3)將a的值寫入內(nèi)存
出錯(cuò)原因的關(guān)鍵就在于第二操作和第三個(gè)操作之間是晨,此時(shí)線程1還沒(méi)來(lái)得及把a(bǔ)的值寫入內(nèi)存肚菠,線程2就把舊值讀走了,這也就造成了a加了兩次罩缴,但是內(nèi)存中的a的值只增加了1蚊逢。這也就是不同步現(xiàn)象层扶。
但是如果說(shuō)我們使用了Synchronized關(guān)鍵字之后呢?
public class SynTest01 implements Runnable{
static int a=0;
Object object = new Object();
public static void main(String[] args) throws InterruptedException {
SynTest01 syn= new SynTest01();
Thread thread1 = new Thread(syn);
Thread thread2 = new Thread(syn);
thread1.start();thread1.join();
thread2.start();thread2.join();
System.out.println(a);
}
@Override
public void run() {
synchronized (object) {
for(int i=0;i<1000;i++) {
a++;
}
}//結(jié)束
}
}
現(xiàn)在我們使用synchronized關(guān)鍵字把這一塊代碼鎖住烙荷,不管你怎么輸出都是2000了镜会,鎖住之后,同一時(shí)刻只有一個(gè)線程進(jìn)入终抽。也就不會(huì)發(fā)生上面a寫操作不同步的現(xiàn)象了戳表。
現(xiàn)在相信你開(kāi)始覺(jué)得synchronized關(guān)鍵字的確很實(shí)用,可以解決多線程中的很多問(wèn)題拿诸。上面這個(gè)小例子只是帶我們?nèi)ズ?jiǎn)單的認(rèn)識(shí)一下扒袖,下面我們就來(lái)看看其詳細(xì)的使用。
二亩码、使用
對(duì)于synchronized關(guān)鍵字來(lái)說(shuō)季率,一共可以分為兩類:對(duì)象鎖和類鎖。
我們一個(gè)一個(gè)來(lái)看如何使用描沟。
1飒泻、對(duì)象鎖
對(duì)于對(duì)象鎖來(lái)說(shuō),又可以分為兩個(gè)吏廉,一個(gè)是方法鎖泞遗,一個(gè)是同步代碼塊鎖。
(1)同步代碼塊鎖
同步代碼塊鎖主要是對(duì)代碼塊進(jìn)行加鎖席覆,其實(shí)已經(jīng)演示過(guò)了史辙,就是上面的那個(gè)案例。不過(guò)為了保持一致我們?cè)倥e一個(gè)例子佩伤。
public class SynTest01 implements Runnable {
Object object = new Object();
public static void main(String[] args) throws InterruptedException {
SynTest01 syn = new SynTest01();
Thread thread1 = new Thread(syn);
Thread thread2 = new Thread(syn);
thread1.start();
thread2.start();
//線程1和線程2只要有一個(gè)還存活就一直執(zhí)行
while (thread1.isAlive() || thread2.isAlive()) {}
System.out.println("main程序運(yùn)行結(jié)束");
}
@Override
public void run() {
synchronized (object) {
try {
System.out.println(Thread.currentThread().getName()
+ "線程執(zhí)行了run方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()
+ "執(zhí)行2秒鐘之后完畢");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在這個(gè)例子中聊倔,我們使用了synchronized鎖住了run方法中的代碼塊。表示同一時(shí)刻只有一個(gè)線程能夠進(jìn)入代碼塊生巡。就好比是去醫(yī)院掛號(hào)耙蔑,前面一個(gè)人辦完了業(yè)務(wù),下一個(gè)人才開(kāi)始孤荣。
在這里面我們看到甸陌,線程1和線程2使用的是同一個(gè)鎖,也就是我們new的Object盐股。如果我們讓線程1和線程2每一個(gè)人擁有一個(gè)鎖對(duì)象呢钱豁?
public class SynTest01 implements Runnable {
Object object1 = new Object();
Object object2 = new Object();
public static void main(String[] args) throws InterruptedException {
SynTest01 syn = new SynTest01();
Thread thread1 = new Thread(syn);
Thread thread2 = new Thread(syn);
thread1.start();
thread2.start();
//線程1和線程2只要有一個(gè)還存活就一直執(zhí)行
while (thread1.isAlive() || thread2.isAlive()) {}
System.out.println("main程序運(yùn)行結(jié)束");
}
?
@Override
public void run() {
synchronized (object1) {
try {
System.out.println(Thread.currentThread().getName()
+ "線程執(zhí)行了object1");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()
+ "執(zhí)行object1完畢");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (object2) {
try {
System.out.println(Thread.currentThread().getName()
+ "線程執(zhí)行object2");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()
+ "執(zhí)行object2完畢");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
現(xiàn)在線程1和線程2每個(gè)人擁有一把鎖,去訪問(wèn)不同的方法資源遂庄。這時(shí)候會(huì)出現(xiàn)什么情況呢寥院?
我們同樣用一張圖看一下其原理。
也就是說(shuō)涛目,相當(dāng)于兩個(gè)業(yè)務(wù)有倆窗口都可以辦理秸谢,但是兩個(gè)任務(wù)都需要排隊(duì)辦理凛澎。
同步代碼塊鎖總結(jié):
同步代碼塊鎖主要是對(duì)代碼塊進(jìn)行加鎖,此時(shí)同一時(shí)刻只能有一個(gè)線程獲取到該資源估蹄,要注意每一把鎖只負(fù)責(zé)當(dāng)前的代碼塊塑煎,其他的代碼塊不管。
以上就是同步代碼快的使用方法臭蚁。下面我們看對(duì)象鎖的另外一種形式最铁,那就是方法鎖。這里的方法鎖指代的是普通方法垮兑。
(2)方法鎖
方法鎖相比較同步代碼塊鎖就簡(jiǎn)單很多了冷尉,就是在普通方法上添加synchronized關(guān)鍵字修飾即可。
public class SynTest2 implements Runnable {
public static void main(String[] args) throws InterruptedException {
SynTest2 syn = new SynTest2();
Thread thread1 = new Thread(syn);
Thread thread2 = new Thread(syn);
thread1.start();
thread2.start();
// 線程1和線程2只要有一個(gè)還存活就一直執(zhí)行
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("main程序運(yùn)行結(jié)束");
}
@Override
public void run() {
method();
}
public synchronized void method() {
try {
System.out.println(Thread.currentThread().getName() + "進(jìn)入到了方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "執(zhí)行完畢");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在這個(gè)例子中我們使用兩個(gè)線程對(duì)同一個(gè)普通方法進(jìn)行訪問(wèn)系枪,結(jié)果可想而知雀哨,也就是同一時(shí)刻只能有一個(gè)線程進(jìn)入到此方法。我們運(yùn)行一下私爷,看一下結(jié)果雾棺。
跟我們預(yù)想的一樣,很簡(jiǎn)單衬浑。不過(guò)我們想過(guò)一個(gè)問(wèn)題沒(méi)有捌浩,此時(shí)我們synchronized關(guān)鍵字加了一把鎖,這個(gè)鎖指代是誰(shuí)呢工秩?像同步代碼塊鎖synchronized (object)尸饺,這里面都有object,但是方法鎖是誰(shuí)呢助币?
答案就是this對(duì)象侵佃,也就是說(shuō)我們?cè)诜椒ㄦi里面synchronized其實(shí)鎖的就是當(dāng)前this對(duì)象。我們?nèi)绾稳ヲ?yàn)證this鎖的存在呢奠支?不如我們?cè)倥e一個(gè)例子:
public class SynTest3 implements Runnable {
public static void main(String[] args) throws InterruptedException {
SynTest3 syn = new SynTest3();
Thread thread1 = new Thread(syn);
Thread thread2 = new Thread(syn);
thread1.start();
thread2.start();
// 線程1和線程2只要有一個(gè)還存活就一直執(zhí)行
while (thread1.isAlive() || thread2.isAlive()) {}
System.out.println("main程序運(yùn)行結(jié)束");
}
@Override
public void run() {
method1();
method2();
}
public synchronized void method1() {
try {
System.out.println(Thread.currentThread().getName() + "進(jìn)入到了方法1");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "離開(kāi)方法1,并釋放鎖");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void method2() {
try {
System.out.println(Thread.currentThread().getName() + "進(jìn)入到了方法2");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "離開(kāi)方法2抚芦,并釋放鎖");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面這個(gè)例子中倍谜,我們定義了兩個(gè)synchronized關(guān)鍵字修飾的方法method1和method2,然后讓兩個(gè)線程同時(shí)運(yùn)行叉抡,我們測(cè)試一下看看會(huì)出現(xiàn)什么結(jié)果:
從結(jié)果來(lái)看尔崔,會(huì)發(fā)現(xiàn)不管是method1還是method2,同一個(gè)時(shí)刻兩個(gè)方法只能有一個(gè)線程在運(yùn)行褥民。這也就是this鎖導(dǎo)致的季春。我們?cè)俳o一張圖描述一下其原理。
現(xiàn)在應(yīng)該明白了吧消返,這也就驗(yàn)證了方法鎖的存在载弄。也驗(yàn)證了方法鎖的原理耘拇。下面我們繼續(xù)。討論一下類鎖宇攻。
2惫叛、類鎖
上面的鎖都是對(duì)象鎖,下面我們看看類鎖逞刷。類鎖其實(shí)也有兩種形式嘉涌,一種是static方法鎖,一種是class鎖夸浅。
(1)static方法鎖
在java中仑最,java的類對(duì)象可能有無(wú)數(shù)個(gè),但是類卻只有一個(gè)帆喇。首先我們看第一種形式警医。
public class SynTest4 implements Runnable {
public static void main(String[] args) throws InterruptedException {
SynTest4 instance1 = new SynTest4();
SynTest4 instance2 = new SynTest4();
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
System.out.println("main程序運(yùn)行結(jié)束");
}
@Override
public void run() {
method1();
}
public static synchronized void method1() {
try {
System.out.println(Thread.currentThread().getName() + "進(jìn)入到了靜態(tài)方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "離開(kāi)靜態(tài)方法,并釋放鎖");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在這個(gè)例子中我們定義了兩個(gè)不同的對(duì)象instance1和instance2番枚。分別去執(zhí)行了method1法严。會(huì)出現(xiàn)什么結(jié)果呢?
如果我們把static關(guān)鍵字去掉葫笼,很明顯現(xiàn)在就是普通方法了深啤,如果我們?cè)偃ミ\(yùn)行,由于instance1和instance2是兩個(gè)不同的對(duì)象路星,那么也就是兩個(gè)不同的this鎖溯街,這時(shí)候就能隨便進(jìn)入了。我們?nèi)サ魋tatic關(guān)鍵字之后運(yùn)行一下:
現(xiàn)在看到了洋丐,由于是兩個(gè)不同的this鎖呈昔,所以都能進(jìn)入,就好比是一個(gè)門有兩把鑰匙友绝,每一把都能打開(kāi)門堤尾。
(2)class鎖
這種用法我們直接看例子再來(lái)分析一下:
public class SynTest5 implements Runnable {
public static void main(String[] args) throws InterruptedException {
SynTest5 instance1 = new SynTest5();
SynTest5 instance2 = new SynTest5();
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
// 線程1和線程2只要有一個(gè)還存活就一直執(zhí)行
while (thread1.isAlive() || thread2.isAlive()) {}
System.out.println("main程序運(yùn)行結(jié)束");
}
@Override
public void run() {
method1();
}
public void method1() {
synchronized (SynTest5.class) {
try {
System.out.println(Thread.currentThread().getName() + "進(jìn)入到了方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "離開(kāi)方法");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在這個(gè)例子中我們使用了同步代碼塊,不過(guò)synchronized關(guān)鍵字包裝的可不是object了迁客,而是SynTest5.class郭宝。我們還定義了兩個(gè)不同的對(duì)象實(shí)例instance1和instance2。運(yùn)行一下我們會(huì)發(fā)現(xiàn)掷漱,線程1和線程2依然會(huì)依次執(zhí)行粘室。
以上就是synchronized關(guān)鍵字的幾種常見(jiàn)的用法,到這里我們來(lái)一個(gè)總結(jié):
對(duì)于同步不同步卜范,關(guān)鍵點(diǎn)在于鎖衔统,兩個(gè)線程執(zhí)行的是同一把鎖,那么就依次排隊(duì)等候,兩個(gè)線程執(zhí)行的不是同一把鎖锦爵,那就各干各的事舱殿。
基本的使用我們也講完了,下面我們進(jìn)入下一個(gè)專題棉浸,那就是我們需要注意的事項(xiàng)怀薛。這是面試常考的一個(gè)問(wèn)題迷郑,不管是機(jī)試還是面試枝恋。
三、6個(gè)常見(jiàn)的使用情況
我們先給出這6種常見(jiàn)的情況嗡害,然后一個(gè)一個(gè)分析焚碌。
1、兩個(gè)線程同時(shí)訪問(wèn)一個(gè)對(duì)象的同步方法霸妹。
2十电、兩個(gè)線程訪問(wèn)的是兩個(gè)對(duì)象的同步方法。
3叹螟、兩個(gè)線程訪問(wèn)的是synchronized的靜態(tài)方法鹃骂。
4、兩個(gè)線程同時(shí)訪問(wèn)同步方法與非同步方法罢绽。
5畏线、一個(gè)線程訪問(wèn)一個(gè)類的兩個(gè)普通同步方法。
6良价、同時(shí)訪問(wèn)靜態(tài)同步方法和非靜態(tài)同步方法寝殴。
為了對(duì)這6種情況做到心中有數(shù),不至于搞混了明垢,我們畫(huà)一張圖蚣常,對(duì)每一種情況進(jìn)行分析。
上面是框架圖痊银,下面我們基于開(kāi)始來(lái)分析:
1抵蚊、兩個(gè)線程同時(shí)訪問(wèn)一個(gè)對(duì)象的同步方法
這種情況對(duì)應(yīng)于以下這張圖:
這種情況很簡(jiǎn)單,我們?cè)谏厦嬉惭菔具^(guò)溯革,結(jié)果就是同一個(gè)時(shí)刻只能有一個(gè)方法進(jìn)入泌射。這里就不再演示了。
2鬓照、兩個(gè)線程訪問(wèn)的是兩個(gè)對(duì)象的同步方法
這種情況對(duì)應(yīng)于下面這種:
也就是一個(gè)方法有兩把鎖,線程1和線程2互不干擾的訪問(wèn)孤紧。鎖是不起作用的豺裆。
3、兩個(gè)線程訪問(wèn)的是synchronized的靜態(tài)方法
這種情況對(duì)應(yīng)于下面這種情況:
我們對(duì)這種情況來(lái)測(cè)試一下吧。
public class SynTest6 implements Runnable {
public static void main(String[] args) throws InterruptedException {
SynTest6 instance1 = new SynTest6();
SynTest6 instance2 = new SynTest6();
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
}
@Override
public void run() {
method1();
}
public synchronized static void method1() {
try {
System.out.println(Thread.currentThread().getName() + "進(jìn)入到了靜態(tài)方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "離開(kāi)靜態(tài)方法臭猜,并釋放鎖");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在這個(gè)例子中我們實(shí)例化了兩個(gè)對(duì)象instance1和instance2躺酒,并且存放在了兩個(gè)不同的線程中,我們測(cè)試一下訪問(wèn)同一個(gè)static同步方法你會(huì)發(fā)現(xiàn)蔑歌。即使是實(shí)例不同羹应,鎖也會(huì)生效,也就是同一時(shí)刻只能有一個(gè)線程進(jìn)去次屠。
4园匹、兩個(gè)線程同時(shí)訪問(wèn)同步方法與非同步方法
這種情況對(duì)應(yīng)于下面這張圖:
我們對(duì)這種情況使用代碼進(jìn)行演示一遍:
public class SynTest7 implements Runnable {
public static void main(String[] args) throws InterruptedException {
SynTest7 instance1 = new SynTest7();
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance1);
thread1.start();
thread2.start();
}
@Override
public void run() {
method1();
method2();
}
public synchronized void method1() {
try {
System.out.println(Thread.currentThread().getName() + "進(jìn)入到了同步方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "離開(kāi)同步方法");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void method2() {
System.out.println(Thread.currentThread().getName() + "進(jìn)入了普通方法");
System.out.println(Thread.currentThread().getName() + "離開(kāi)了普通方法");
}
}
在上面的代碼中,我們定義一個(gè)對(duì)象劫灶,但是使用了兩個(gè)線程去分別同時(shí)訪問(wèn)同步和非同步方法裸违。我們看結(jié)果:
也就是說(shuō),同步方法依然會(huì)同步執(zhí)行本昏,非同步方法不會(huì)受到任何影響供汛。
5、一個(gè)線程訪問(wèn)一個(gè)類的兩個(gè)普通同步方法
這種情況對(duì)應(yīng)于下面這張圖:
我們代碼來(lái)測(cè)試一下:
public class SynTest8 implements Runnable {
public static void main(String[] args) throws InterruptedException {
SynTest8 instance1 = new SynTest8();
Thread thread1 = new Thread(instance1);
thread1.start();
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("Thread-0")) {
method1();
}else {
method2();
}
}
public synchronized void method1() {
try {
System.out.println(Thread.currentThread().getName() + "進(jìn)入到了同步方法1");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "離開(kāi)同步方法1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void method2() {
try {
System.out.println(Thread.currentThread().getName() + "進(jìn)入到了同步方法2");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "離開(kāi)同步方法2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面這個(gè)例子我們創(chuàng)建了一個(gè)對(duì)象instance1涌穆,然后使用一個(gè)線程分別去訪問(wèn)同步方法1和同步方法2怔昨。結(jié)果呢可想而知,所一定會(huì)失效宿稀。因?yàn)樵谝婚_(kāi)始我們已經(jīng)驗(yàn)證了趁舀,此時(shí)同步方法1和同步方法2中synchronized鎖的就是this對(duì)象,所以是同一把鎖原叮。當(dāng)然會(huì)生效赫编。
6、同時(shí)訪問(wèn)靜態(tài)同步方法和非靜態(tài)同步方法
這種情況對(duì)應(yīng)于下面這張圖:
我們使用代碼來(lái)測(cè)試一波:
public class SynTest9 implements Runnable {
public static void main(String[] args) throws InterruptedException {
SynTest9 instance1 = new SynTest9();
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance1);
thread1.start();thread2.start();
}
@Override
public void run() {
method1();
method2();
}
public synchronized void method1() {
try {
System.out.println(Thread.currentThread().getName() + "進(jìn)入到了同步方法1");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "離開(kāi)同步方法1");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized static void method2() {
try {
System.out.println(Thread.currentThread().getName() + "進(jìn)入到了靜態(tài)同步方法2");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "離開(kāi)靜態(tài)同步方法2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在上面的代碼中奋隶,我們創(chuàng)建了一個(gè)instance實(shí)例擂送,使用兩個(gè)線程同時(shí)訪問(wèn)普通同步方法和靜態(tài)同步方法。下面運(yùn)行一下唯欣,看看輸出結(jié)果:
上面輸出結(jié)果表明普通同步方法和靜態(tài)同步方法是沒(méi)有關(guān)聯(lián)的嘹吨,這是為什么呢?這是因?yàn)槠胀ㄍ椒椒ǖ逆i是對(duì)象境氢,但是靜態(tài)同步方法的鎖是類蟀拷,所以這是兩把鎖。鎖自然也就是失效了萍聊。
四问芬、性質(zhì)
讀到這里,不知道你是不是已經(jīng)很疲憊了寿桨,反正我寫的是很難受此衅,不過(guò)剩下的這些部分才是精華强戴,也是面試或者是工作中提升你zhuangbility的一個(gè)點(diǎn)。希望你一定要注意挡鞍。認(rèn)真讀下去骑歹。
對(duì)于synchronized關(guān)鍵字主要有兩個(gè)性質(zhì):可重入性質(zhì)和不可中斷性質(zhì)。我們分別來(lái)看墨微。
1道媚、可重入性質(zhì)
什么是可重入呢?指的是同一線程的外層函數(shù)獲得鎖之后翘县,內(nèi)層函數(shù)可以直接再次獲取該鎖最域。我們舉一個(gè)例子來(lái)說(shuō)明,一句話吃著碗里的看著鍋里的炼蹦。嘴里面還沒(méi)吃完就繼續(xù)再去拿吃的羡宙。這就是可重入。不可重入的意思正好相反掐隐,你吃完了這碗飯才能盛下一碗狗热。
可重入的程度可以細(xì)分為三種情況,我們分別測(cè)試一下:
(1)同一個(gè)方法中是不是可重入的虑省。就好比是遞歸調(diào)用同步方法匿刮。
(2)不同的方法是不是可重入的。就好比是一個(gè)同步方法調(diào)用另外一個(gè)同步方法探颈。
(3)不同的類方法是不是可重入的熟丸。
下面我們就是用代碼來(lái)測(cè)試一遍:
(1)同一個(gè)方法是不是可重入的
public class SynTest10 {
private int a=1;
public static void main(String[] args) throws InterruptedException {
SynTest10 instance1 = new SynTest10();
instance1.method1();
}
public synchronized void method1() {
System.out.println("method1: a= " + a);
if(a == 3) {
return ;
}else {
a++;
method1();
}
}
}
代碼很簡(jiǎn)單,也就是我們定義了一個(gè)變量a伪节,只要a不等于3光羞,就一直遞歸調(diào)用方法method1。我們可以看一下運(yùn)行結(jié)果怀大。
也就是說(shuō)在同一個(gè)方法中是可重入的纱兑。下面我們接著測(cè)試。
(2)不同的方法是不是可重入的
public class SynTest10 {
public static void main(String[] args) throws InterruptedException {
SynTest10 instance1 = new SynTest10();
instance1.method1();
}
public synchronized void method1() {
System.out.println("method1");
method2();
}
public synchronized void method2() {
System.out.println("method2" );
}
}
我們?cè)谕椒椒?中調(diào)用了同步方法2化借。我們同樣測(cè)試一下潜慎。
method1和method2可以依次輸出,說(shuō)明了在不同的方法中也是可重入的蓖康。
(3)铐炫、不同的類方法是不是可重入的
既然是不同的類,那么我們就在這里定義兩個(gè)類蒜焊,一個(gè)是Father倒信,一個(gè)是Son。我們讓son調(diào)用father中的方法泳梆。
public class Father{
public synchronized void father() {
System.out.println("父親");
}
}
class Son extends Father{
public static void main(String[] args) {
Son instance1 = new Son();
instance1.son();
}
public synchronized void son() {
System.out.println("兒子");
super.father();
}
}
在這里son類中使用super.father()調(diào)用了父類中的synchronized方法鳖悠,我們測(cè)試一下看看輸出結(jié)果:
2唆迁、不可中斷性質(zhì)
不可中斷的意思你可以這樣理解,別人正在打游戲竞穷,你也想玩,你必須要等別人不想玩了你才能去鳞溉。在java中表示一旦這個(gè)鎖被別人搶走了瘾带,你必須等待。等別的線程釋放了鎖熟菲,你才可以拿到看政。否則就一直等下去。
這一點(diǎn)看起來(lái)是個(gè)有點(diǎn)但其實(shí)在某些場(chǎng)景下弊端超級(jí)大抄罕,因?yàn)榧偃缒玫芥i得線程永遠(yuǎn)的不釋放允蚣,那你就要永遠(yuǎn)的等下去。
五呆贿、底層原理
對(duì)于原理嚷兔,最好的方式就是深入到JVM中去。我們可以編譯看看其字節(jié)碼文件做入,再來(lái)分析冒晰,因此在這里舉一個(gè)最簡(jiǎn)單的例子。
1竟块、定義一個(gè)簡(jiǎn)單例子
public class SynTest11 {
private Object object = new Object();
public void test() {
synchronized(object){
System.out.println("java的架構(gòu)師技術(shù)棧");
}
}
}
2壶运、分析
分析的步驟很簡(jiǎn)單,我們通過(guò)反編譯字節(jié)碼文件浪秘。記住我們的類名是SynTest11蒋情。
先編譯生成字節(jié)碼文件。
然后耸携,我們?cè)俜淳幾g字節(jié)碼文件棵癣。
以上我們知道其是就是設(shè)置了一個(gè)監(jiān)控器monitor。線程進(jìn)來(lái)那就是monitorenter违帆,線程離開(kāi)是monitorexit浙巫。這就是synchronized關(guān)鍵字最基本的原理。
3刷后、可重入原理
在上面我們?cè)岬娇芍厝氲男再|(zhì)的畴,那么synchronized關(guān)鍵字是如何保證的呢?其是工作是由我們的jvm來(lái)完成的尝胆,線程第一次給對(duì)象加鎖的時(shí)候丧裁,計(jì)數(shù)為1,以后這個(gè)線程再次獲取鎖的時(shí)候含衔,計(jì)數(shù)會(huì)依次增加煎娇。同理二庵,任務(wù)離開(kāi)的時(shí)候,相應(yīng)的計(jì)數(shù)器也會(huì)減少缓呛。
4催享、從java內(nèi)存模型分析
java內(nèi)存模型不是真正存在的,但是我們可以給出一個(gè)內(nèi)存模型哟绊。synchronized關(guān)鍵字因妙,會(huì)對(duì)同步的代碼會(huì)先寫到工作內(nèi)存,等synchronized修飾的代碼塊一結(jié)束票髓,就會(huì)寫入到主內(nèi)存攀涵,這樣保證了同步。
六洽沟、缺陷
synchronized關(guān)鍵字既有優(yōu)點(diǎn)也有缺點(diǎn)以故,而且缺點(diǎn)賊多,所以后來(lái)出現(xiàn)了比他更好的鎖裆操。下面我們就來(lái)分析一下怒详,這也是面試常問(wèn)問(wèn)題。
1跷车、效率低
我們之前曾經(jīng)分析過(guò)synchronized關(guān)鍵字是不可中斷的棘利,這也就意味著一個(gè)等待的線程如果不能獲取到鎖將會(huì)一直等待,而不能再去做其他的事了朽缴。
這里也說(shuō)明了對(duì)synchronized關(guān)鍵字的一個(gè)改進(jìn)措施善玫,那就是設(shè)置超時(shí)時(shí)間,如果一個(gè)線程長(zhǎng)時(shí)間拿不到鎖密强,就可以去做其他事情了茅郎。
2、不夠靈活
加鎖和解鎖的時(shí)候或渤,每個(gè)鎖只能有一個(gè)對(duì)象處理系冗,這對(duì)于目前分布式等思想格格不入。
3薪鹦、無(wú)法知道是否成功獲取到鎖
也就是我們的鎖如果獲取到了掌敬,我們無(wú)法得知。既然無(wú)法得知我們也就很不容易進(jìn)行改進(jìn)池磁。
既然synchronized有這么多缺陷奔害。所以才出現(xiàn)了各種各樣的鎖。
七地熄、總結(jié)
終于寫完了华临,synchronized涉及到的知識(shí)點(diǎn),以及能夠引出來(lái)的知識(shí)點(diǎn)超級(jí)多端考,不過(guò)只有理解synchronized關(guān)鍵字雅潭,我們才可以更加深入的學(xué)習(xí)揭厚。本篇文章不可能面面俱到,只能說(shuō)列出來(lái)一些常見(jiàn)的知識(shí)點(diǎn)扶供。更加深入的理解我也會(huì)在后續(xù)的文章中指出筛圆。感謝大家的支持。