<h1>一. synchronized 同步代碼關鍵字</h1>
此關鍵字為java中非常重要的關鍵字類型,當用它來修飾一個方法或者代碼塊的時候,能夠保證所修飾的方法或者代碼塊在同一時刻最多只有一個線程在執行該段代碼。
1) 當兩個或者多個并發線程同時訪問一個object中的synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行,其他線程必須要等到當前線程執行完這個代碼塊之后才能繼續執行該代碼塊。
2) 然而,當一個線程訪問object中的synchronized(this)同步代碼塊時,其他線程仍然可以調用object中的其它非synchronized(this)同步代碼塊。
3) 尤其關鍵的是,當一個線程訪問object中的一個synchronized(this)同步代碼塊時,其他線程對于object中的其它synchronized(this)同步代碼塊的訪問,都將被阻塞。
4) 第3條中的說明同樣適用于其它同步代碼塊,也就是說,當一個線程訪問object中的一個synchronized(this)同步代碼塊時,它就獲得了整個object的對象鎖,因此,其他所有線程對于object的所有同步代碼塊的訪問都將被暫時阻塞,直至當前線程執行完這個代碼塊。
5) 上述的規則對于其他對象鎖也同樣適用。
對于synchronized關鍵字的個人理解
synchronized關鍵字,包括兩種用法,一種是synchronized方法,另一種是synchronized塊。
1. synchronized方法###
如果我們想要聲明一個synchronized方法,只需要在聲明方法的時候,加入synchronized關鍵字即可,如:
public synchronized void method2()
每一個類實例都對應一把鎖,每個synchronized方法的執行都必須要獲得調用該方法的類的實例的鎖,才能執行,否則,線程就會阻塞。而方法一旦開始執行,就會獨占該鎖,直到方法執行完成返回后釋放該鎖,然后阻塞的線程就獲取鎖,然后調用方法執行。
這種機制確保了同一時刻對于每一個類的實例來講,只有一個synchronized方法處于正在執行狀態,因為同一時間最多只能有一個線程獲取到類的實例的鎖。這能確保類成員變量的訪問沖突問題,前提是我們將所有可能沖突的方法聲明為synchronized方法。
在java中,不僅僅是類的實例,類也有鎖,我們可以將類的靜態變量聲明上添加synchronized關鍵字,這樣就能控制線程對于類靜態成員變量的訪問。
缺點:將方法聲明為synchronized會影響效率。例如,如果我們想線程類的run()方法定義為synchronized的,則在線程的整個生命周期內,它都會一直運行,且它無法調用本類任何synchronized方法,因為一直占用鎖。所以我們可以將訪問成員變量部分的代碼定義為單獨的方法,然后將這個方法聲明為synchronized即可。
synchronized塊###
我們同樣可以通過使用synchronized關鍵字聲明一個synchronized塊,代碼如下
synchronized (inner) {
//需要進行訪問控制的代碼
}
上述代碼中的inner可以為類,也可以為類的實例,具體實現可以如下所示,此種方式同synchronized進行比較的話,更為靈活,我們可以根據實際的需要對于想要添加鎖的代碼或類加鎖。
<h2>1.示例演示第1條規則,同一時間只能有一個線程在執行同步代碼塊</h2>
public class ThreadTest implements Runnable {
public void run() {
//同步代碼塊,該代碼塊在同一時間只能被一個線程調用,其他想要執行同步代碼塊的線程必須要等待當前線程執行完代碼塊之后才能繼續執行同步代碼塊的內容
synchronized (this) {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized index " + i);
}
}
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
Thread ta = new Thread(threadTest,"Thread ta");
Thread tb = new Thread(threadTest,"Thread tb");
ta.start();
tb.start();
}
}
執行結果為
Thread ta synchronized index 0
Thread ta synchronized index 1
Thread ta synchronized index 2
Thread tb synchronized index 0
Thread tb synchronized index 1
Thread tb synchronized index 2
<h2>2.示例演示第2條規則,同一時間其他線程可以調用對象的其他非同步代碼塊</h2>
public class ThreadTest {
public void synchron() {
synchronized (this) {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
public void noSynchron() {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
threadTest.synchron();
}
}, "ThreadTA");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
threadTest.noSynchron();
}
}, "ThreadTB");
ta.start();
tb.start();
}
}
執行結果為
ThreadTA:3
ThreadTB:3
ThreadTA:2
ThreadTB:2
ThreadTA:1
ThreadTB:1
ThreadTB:0
ThreadTA:0
<h2>3.示例演示第3條規則,我們修改上面代碼的noSynchron方法為同步代碼塊</h2>
代碼如下
public void noSynchron() {
synchronized (this) {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
執行結果為
ThreadTA:3
ThreadTA:2
ThreadTA:1
ThreadTA:0
ThreadTB:3
ThreadTB:2
ThreadTB:1
ThreadTB:0
<h2>4.示例演示第4條規則,我們修改上面代碼的noSynchron方法為如下代碼</h2>
public synchronized void noSynchron() {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
執行結果與上面相同。
<h2>5.示例演示第5條規則,以上規則對于其他對象鎖同樣適用</h2>
public class ThreadTest {
class innerClass {
private void method1() {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " innerClass.method1 " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
private void method2() {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " innerClass.method2 " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
}
public void getInnerClass1(innerClass inner) {
synchronized (inner) {
inner.method1();
}
}
public void getInnerClass2(innerClass inner) {
inner.method2();
}
public static void main(String[] args) {
final ThreadTest threadTest = new ThreadTest();
final innerClass inner = threadTest.new innerClass();
Thread ta = new Thread(new Runnable() {
@Override
public void run() {
threadTest.getInnerClass1(inner);
}
}, "ThreadTA");
Thread tb = new Thread(new Runnable() {
@Override
public void run() {
threadTest.getInnerClass2(inner);
}
}, "ThreadTB");
ta.start();
tb.start();
}
}
執行結果
ThreadTA innerClass.method1 3
ThreadTB innerClass.method2 3
ThreadTA innerClass.method1 2
ThreadTB innerClass.method2 2
ThreadTA innerClass.method1 1
ThreadTB innerClass.method2 1
ThreadTA innerClass.method1 0
ThreadTB innerClass.method2 0
那么,如果我們在method2方法上增加同步方法鎖,則方法變為
private synchronized void method2() {
int i = 4;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " innerClass.method2 " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
執行結果變為
ThreadTA innerClass.method1 3
ThreadTA innerClass.method1 2
ThreadTA innerClass.method1 1
ThreadTA innerClass.method1 0
ThreadTB innerClass.method2 3
ThreadTB innerClass.method2 2
ThreadTB innerClass.method2 1
ThreadTB innerClass.method2 0
至此可以看出,盡管兩個線程訪問了內部類中不同的兩個方法,但是由于都是使用了同步代碼塊synchronized關鍵字進行了標識,因此在調用中,由于ThreadTA先獲得了innerClass類的對象鎖,導致在ThreadTA執行完代碼塊之前,ThreadTB被阻塞,直到ThreadTA執行完代碼塊后,ThreadTB才繼續執行。