Synchronized關(guān)鍵字一直是工作和面試中的重點(diǎn)。這篇文章準(zhǔn)備徹徹底底的從基礎(chǔ)使用到原理缺陷等各個(gè)方面來一個(gè)分析,這篇文章由于篇幅比較長,但是如果你有時(shí)間和耐心,相信會(huì)有一個(gè)比較大的收獲,所以,學(xué)習(xí)請(qǐng)慢慢來。這篇文章主要從以下幾個(gè)方面進(jìn)行分析講解.
1、Synchronized關(guān)鍵字的簡介,主要是為什么要使用Synchronized關(guān)鍵字,極其作用地位。
2、Synchronized關(guān)鍵字的使用,主要是從對(duì)象鎖和類鎖兩個(gè)角度。
3、Synchronized關(guān)鍵字的使用注意事項(xiàng)。分析了6種常見的使用情況。
4、Synchronized關(guān)鍵字的兩個(gè)性質(zhì),主要是可重入性和不可中斷性。
5、Synchronized關(guān)鍵字的底層原理。
6、Synchronized關(guān)鍵字的常見缺陷。
以上我們主要是從這7個(gè)角度來分析Synchronized關(guān)鍵字,每一個(gè)角度說實(shí)話都能單獨(dú)拿出來作為一篇文章來分析。但是由于考慮到文章的連貫性,所以綜合在了一起,循序漸進(jìn)。下面我們就帶著這些問題開始今天的文章。
一、簡介
Synchronized一句話來解釋其作用就是:能夠保證同一時(shí)刻最多只有一個(gè)線程執(zhí)行該段代碼,以達(dá)到并發(fā)安全的效果。也就是說Synchronized就好比是一把鎖,某個(gè)線程把資源鎖住了之后,別人就不能使用了,只有當(dāng)這個(gè)線程用完了別人才能用。
對(duì)于Synchronized關(guān)鍵字來說,它是并發(fā)編程中一個(gè)元老級(jí)角色,也就是說你只要學(xué)習(xí)并發(fā)編程,就必須要學(xué)習(xí)Synchronized關(guān)鍵字。由此可見其地位。
說了這么多,好像我們還沒體驗(yàn)過它的威力。我們就直接舉個(gè)例子,來分析一下。
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。不過如果我們運(yùn)行過之后我們就會(huì)發(fā)現(xiàn),最后的輸出值總是小于2000,這是為什么呢?
這是因?yàn)槲覀冊趫?zhí)行a++的時(shí)候其實(shí)包含了以下三個(gè)操作:
(1)線程1讀取a
(2)線程1將a加1
(3)將a的值寫入內(nèi)存
出錯(cuò)原因的關(guān)鍵就在于第二操作和第三個(gè)操作之間,此時(shí)線程1還沒來得及把a(bǔ)的值寫入內(nèi)存,線程2就把舊值讀走了,這也就造成了a加了兩次,但是內(nèi)存中的a的值只增加了1。這也就是不同步現(xiàn)象。
但是如果說我們使用了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)在相信你開始覺得synchronized關(guān)鍵字的確很實(shí)用,可以解決多線程中的很多問題。上面這個(gè)小例子只是帶我們?nèi)ズ唵蔚恼J(rèn)識(shí)一下,下面我們就來看看其詳細(xì)的使用。
二、使用
對(duì)于synchronized關(guān)鍵字來說,一共可以分為兩類:對(duì)象鎖和類鎖。

我們一個(gè)一個(gè)來看如何使用。
1、對(duì)象鎖
對(duì)于對(duì)象鎖來說,又可以分為兩個(gè),一個(gè)是方法鎖,一個(gè)是同步代碼塊鎖。
(1)同步代碼塊鎖
同步代碼塊鎖主要是對(duì)代碼塊進(jìn)行加鎖,其實(shí)已經(jīng)演示過了,就是上面的那個(gè)案例。不過為了保持一致我們再舉一個(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è)人才開始。

在這里面我們看到,線程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è)人擁有一把鎖,去訪問不同的方法資源。這時(shí)候會(huì)出現(xiàn)什么情況呢?

我們同樣用一張圖看一下其原理。

也就是說,相當(dāng)于兩個(gè)業(yè)務(wù)有倆窗口都可以辦理,但是兩個(gè)任務(wù)都需要排隊(duì)辦理。
同步代碼塊鎖總結(jié):
同步代碼塊鎖主要是對(duì)代碼塊進(jìn)行加鎖,此時(shí)同一時(shí)刻只能有一個(gè)線程獲取到該資源,要注意每一把鎖只負(fù)責(zé)當(dāng)前的代碼塊,其他的代碼塊不管。
以上就是同步代碼快的使用方法。下面我們看對(duì)象鎖的另外一種形式,那就是方法鎖。這里的方法鎖指代的是普通方法。
(2)方法鎖
方法鎖相比較同步代碼塊鎖就簡單很多了,就是在普通方法上添加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)行訪問,結(jié)果可想而知,也就是同一時(shí)刻只能有一個(gè)線程進(jìn)入到此方法。我們運(yùn)行一下,看一下結(jié)果。

跟我們預(yù)想的一樣,很簡單。不過我們想過一個(gè)問題沒有,此時(shí)我們synchronized關(guān)鍵字加了一把鎖,這個(gè)鎖指代是誰呢?像同步代碼塊鎖synchronized (object),這里面都有object,但是方法鎖是誰呢?
答案就是this對(duì)象,也就是說我們在方法鎖里面synchronized其實(shí)鎖的就是當(dāng)前this對(duì)象。我們?nèi)绾稳ヲ?yàn)證this鎖的存在呢?不如我們再舉一個(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() + "離開方法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() + "離開方法2,并釋放鎖");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面這個(gè)例子中,我們定義了兩個(gè)synchronized關(guān)鍵字修飾的方法method1和method2,然后讓兩個(gè)線程同時(shí)運(yùn)行,我們測試一下看看會(huì)出現(xiàn)什么結(jié)果:

從結(jié)果來看,會(huì)發(fā)現(xiàn)不管是method1還是method2,同一個(gè)時(shí)刻兩個(gè)方法只能有一個(gè)線程在運(yùn)行。這也就是this鎖導(dǎo)致的。我們再給一張圖描述一下其原理。

現(xiàn)在應(yīng)該明白了吧,這也就驗(yàn)證了方法鎖的存在。也驗(yàn)證了方法鎖的原理。下面我們繼續(xù)。討論一下類鎖。
2、類鎖
上面的鎖都是對(duì)象鎖,下面我們看看類鎖。類鎖其實(shí)也有兩種形式,一種是static方法鎖,一種是class鎖。
(1)static方法鎖
在java中,java的類對(duì)象可能有無數(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() + "離開靜態(tài)方法,并釋放鎖");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在這個(gè)例子中我們定義了兩個(gè)不同的對(duì)象instance1和instance2。分別去執(zhí)行了method1。會(huì)出現(xiàn)什么結(jié)果呢?

如果我們把static關(guān)鍵字去掉,很明顯現(xiàn)在就是普通方法了,如果我們再去運(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è)門有兩把鑰匙,每一把都能打開門。
(2)class鎖
這種用法我們直接看例子再來分析一下:
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() + "離開方法");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在這個(gè)例子中我們使用了同步代碼塊,不過synchronized關(guān)鍵字包裝的可不是object了,而是SynTest5.class。我們還定義了兩個(gè)不同的對(duì)象實(shí)例instance1和instance2。運(yùn)行一下我們會(huì)發(fā)現(xiàn),線程1和線程2依然會(huì)依次執(zhí)行。
以上就是synchronized關(guān)鍵字的幾種常見的用法,到這里我們來一個(gè)總結(jié):
對(duì)于同步不同步,關(guān)鍵點(diǎn)在于鎖,兩個(gè)線程執(zhí)行的是同一把鎖,那么就依次排隊(duì)等候,兩個(gè)線程執(zhí)行的不是同一把鎖,那就各干各的事。
基本的使用我們也講完了,下面我們進(jìn)入下一個(gè)專題,那就是我們需要注意的事項(xiàng)。這是面試常考的一個(gè)問題,不管是機(jī)試還是面試。
三、6個(gè)常見的使用情況
我們先給出這6種常見的情況,然后一個(gè)一個(gè)分析。
1、兩個(gè)線程同時(shí)訪問一個(gè)對(duì)象的同步方法。
2、兩個(gè)線程訪問的是兩個(gè)對(duì)象的同步方法。
3、兩個(gè)線程訪問的是synchronized的靜態(tài)方法。
4、兩個(gè)線程同時(shí)訪問同步方法與非同步方法。
5、一個(gè)線程訪問一個(gè)類的兩個(gè)普通同步方法。
6、同時(shí)訪問靜態(tài)同步方法和非靜態(tài)同步方法。
為了對(duì)這6種情況做到心中有數(shù),不至于搞混了,我們畫一張圖,對(duì)每一種情況進(jìn)行分析。

上面是框架圖,下面我們基于開始來分析:
1、兩個(gè)線程同時(shí)訪問一個(gè)對(duì)象的同步方法
這種情況對(duì)應(yīng)于以下這張圖:

這種情況很簡單,我們在上面也演示過,結(jié)果就是同一個(gè)時(shí)刻只能有一個(gè)方法進(jìn)入。這里就不再演示了。
2、兩個(gè)線程訪問的是兩個(gè)對(duì)象的同步方法
這種情況對(duì)應(yīng)于下面這種:

也就是一個(gè)方法有兩把鎖,線程1和線程2互不干擾的訪問。鎖是不起作用的。
3、兩個(gè)線程訪問的是synchronized的靜態(tài)方法
這種情況對(duì)應(yīng)于下面這種情況:

我們對(duì)這種情況來測試一下吧。
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() + "離開靜態(tài)方法,并釋放鎖");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在這個(gè)例子中我們實(shí)例化了兩個(gè)對(duì)象instance1和instance2,并且存放在了兩個(gè)不同的線程中,我們測試一下訪問同一個(gè)static同步方法你會(huì)發(fā)現(xiàn)。即使是實(shí)例不同,鎖也會(huì)生效,也就是同一時(shí)刻只能有一個(gè)線程進(jìn)去。
4、兩個(gè)線程同時(shí)訪問同步方法與非同步方法
這種情況對(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() + "離開同步方法");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void method2() {
System.out.println(Thread.currentThread().getName() + "進(jìn)入了普通方法");
System.out.println(Thread.currentThread().getName() + "離開了普通方法");
}
}
在上面的代碼中,我們定義一個(gè)對(duì)象,但是使用了兩個(gè)線程去分別同時(shí)訪問同步和非同步方法。我們看結(jié)果:

也就是說,同步方法依然會(huì)同步執(zhí)行,非同步方法不會(huì)受到任何影響。
5、一個(gè)線程訪問一個(gè)類的兩個(gè)普通同步方法
這種情況對(duì)應(yīng)于下面這張圖:

我們代碼來測試一下:
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() + "離開同步方法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() + "離開同步方法2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面這個(gè)例子我們創(chuàng)建了一個(gè)對(duì)象instance1,然后使用一個(gè)線程分別去訪問同步方法1和同步方法2。結(jié)果呢可想而知,所一定會(huì)失效。因?yàn)樵谝婚_始我們已經(jīng)驗(yàn)證了,此時(shí)同步方法1和同步方法2中synchronized鎖的就是this對(duì)象,所以是同一把鎖。當(dāng)然會(huì)生效。
6、同時(shí)訪問靜態(tài)同步方法和非靜態(tài)同步方法
這種情況對(duì)應(yīng)于下面這張圖:

我們使用代碼來測試一波:
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() + "離開同步方法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() + "離開靜態(tài)同步方法2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在上面的代碼中,我們創(chuàng)建了一個(gè)instance實(shí)例,使用兩個(gè)線程同時(shí)訪問普通同步方法和靜態(tài)同步方法。下面運(yùn)行一下,看看輸出結(jié)果:

上面輸出結(jié)果表明普通同步方法和靜態(tài)同步方法是沒有關(guān)聯(lián)的,這是為什么呢?這是因?yàn)槠胀ㄍ椒椒ǖ逆i是對(duì)象,但是靜態(tài)同步方法的鎖是類,所以這是兩把鎖。鎖自然也就是失效了。
四、性質(zhì)
讀到這里,不知道你是不是已經(jīng)很疲憊了,反正我寫的是很難受,不過剩下的這些部分才是精華,也是面試或者是工作中提升你zhuangbility的一個(gè)點(diǎn)。希望你一定要注意。認(rèn)真讀下去。
對(duì)于synchronized關(guān)鍵字主要有兩個(gè)性質(zhì):可重入性質(zhì)和不可中斷性質(zhì)。我們分別來看。
1、可重入性質(zhì)
什么是可重入呢?指的是同一線程的外層函數(shù)獲得鎖之后,內(nèi)層函數(shù)可以直接再次獲取該鎖。我們舉一個(gè)例子來說明,一句話吃著碗里的看著鍋里的。嘴里面還沒吃完就繼續(xù)再去拿吃的。這就是可重入。不可重入的意思正好相反,你吃完了這碗飯才能盛下一碗。
可重入的程度可以細(xì)分為三種情況,我們分別測試一下:
(1)同一個(gè)方法中是不是可重入的。就好比是遞歸調(diào)用同步方法。
(2)不同的方法是不是可重入的。就好比是一個(gè)同步方法調(diào)用另外一個(gè)同步方法。
(3)不同的類方法是不是可重入的。
下面我們就是用代碼來測試一遍:
(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();
}
}
}
代碼很簡單,也就是我們定義了一個(gè)變量a,只要a不等于3,就一直遞歸調(diào)用方法method1。我們可以看一下運(yùn)行結(jié)果。

也就是說在同一個(gè)方法中是可重入的。下面我們接著測試。
(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" );
}
}
我們在同步方法1中調(diào)用了同步方法2。我們同樣測試一下。

method1和method2可以依次輸出,說明了在不同的方法中也是可重入的。
(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方法,我們測試一下看看輸出結(jié)果:

2、不可中斷性質(zhì)
不可中斷的意思你可以這樣理解,別人正在打游戲,你也想玩,你必須要等別人不想玩了你才能去。在java中表示一旦這個(gè)鎖被別人搶走了,你必須等待。等別的線程釋放了鎖,你才可以拿到。否則就一直等下去。
這一點(diǎn)看起來是個(gè)有點(diǎn)但其實(shí)在某些場景下弊端超級(jí)大,因?yàn)榧偃缒玫芥i得線程永遠(yuǎn)的不釋放,那你就要永遠(yuǎn)的等下去。
五、底層原理
對(duì)于原理,最好的方式就是深入到JVM中去。我們可以編譯看看其字節(jié)碼文件,再來分析,因此在這里舉一個(gè)最簡單的例子。
1、定義一個(gè)簡單例子
public class SynTest11 {
private Object object = new Object();
public void test() {
synchronized(object){
System.out.println("java的架構(gòu)師技術(shù)棧");
}
}
}
2、分析
分析的步驟很簡單,我們通過反編譯字節(jié)碼文件。記住我們的類名是SynTest11。
先編譯生成字節(jié)碼文件。

然后,我們再反編譯字節(jié)碼文件。

以上我們知道其是就是設(shè)置了一個(gè)監(jiān)控器monitor。線程進(jìn)來那就是monitorenter,線程離開是monitorexit。這就是synchronized關(guān)鍵字最基本的原理。
3、可重入原理
在上面我們曾提到可重入的性質(zhì),那么synchronized關(guān)鍵字是如何保證的呢?其是工作是由我們的jvm來完成的,線程第一次給對(duì)象加鎖的時(shí)候,計(jì)數(shù)為1,以后這個(gè)線程再次獲取鎖的時(shí)候,計(jì)數(shù)會(huì)依次增加。同理,任務(wù)離開的時(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)賊多,所以后來出現(xiàn)了比他更好的鎖。下面我們就來分析一下,這也是面試常問問題。
1、效率低
我們之前曾經(jīng)分析過synchronized關(guān)鍵字是不可中斷的,這也就意味著一個(gè)等待的線程如果不能獲取到鎖將會(huì)一直等待,而不能再去做其他的事了。
這里也說明了對(duì)synchronized關(guān)鍵字的一個(gè)改進(jìn)措施,那就是設(shè)置超時(shí)時(shí)間,如果一個(gè)線程長時(shí)間拿不到鎖,就可以去做其他事情了。
2、不夠靈活
加鎖和解鎖的時(shí)候,每個(gè)鎖只能有一個(gè)對(duì)象處理,這對(duì)于目前分布式等思想格格不入。
3、無法知道是否成功獲取到鎖
也就是我們的鎖如果獲取到了,我們無法得知。既然無法得知我們也就很不容易進(jìn)行改進(jìn)。
既然synchronized有這么多缺陷。所以才出現(xiàn)了各種各樣的鎖。
七、總結(jié)
終于寫完了,synchronized涉及到的知識(shí)點(diǎn),以及能夠引出來的知識(shí)點(diǎn)超級(jí)多,不過只有理解synchronized關(guān)鍵字,我們才可以更加深入的學(xué)習(xí)。本篇文章不可能面面俱到,只能說列出來一些常見的知識(shí)點(diǎn)。更加深入的理解我也會(huì)在后續(xù)的文章中指出。感謝大家的支持。
