本章,會對synchronized關鍵字進行介紹。涉及到的内容包括:
synchronized原理
synchronized基本規則
synchronized方法 和 synchronized代碼塊
執行個體鎖 和 全局鎖
在java中,每一個對象有且僅有一個同步鎖。這也意味着,同步鎖是依賴于對象而存在。
當我們調用某對象的synchronized方法時,就擷取了該對象的同步鎖。例如,synchronized(obj)就擷取了“obj這個對象”的同步鎖。
不同線程對同步鎖的通路是互斥的。也就是說,某時間點,對象的同步鎖隻能被一個線程擷取到!通過同步鎖,我們就能在多線程中,實作對“對象/方法”的互斥通路。 例如,現在有兩個線程A和線程B,它們都會通路“對象obj的同步鎖”。假設,在某一時刻,線程A擷取到“obj的同步鎖”并在執行一些操作;而此時,線程B也企圖擷取“obj的同步鎖” —— 線程B會擷取失敗,它必須等待,直到線程A釋放了“該對象的同步鎖”之後線程B才能擷取到“obj的同步鎖”進而才可以運作。
我們将synchronized的基本規則總結為下面3條,并通過執行個體對它們進行說明。
第一條:
當一個線程通路“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程對“該對象”的該“synchronized方法”或者“synchronized代碼塊”的通路将被阻塞。
第二條:
當一個線程通路“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程仍然可以通路“該對象”的非同步代碼塊。
第三條:
當一個線程通路“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程對“該對象”的其他的“synchronized方法”或者“synchronized代碼塊”的通路将被阻塞。
第一條:
下面是“synchronized代碼塊”對應的示範程式。
class MyRunable implements Runnable {
@Override
public void run() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class Demo1_1 {
public static void main(String[] args) {
Runnable demo = new MyRunable(); // 建立“Runnable對象”
Thread t1 = new Thread(demo, "t1"); // 建立“線程t1”, t1是基于demo這個Runnable對象
Thread t2 = new Thread(demo, "t2"); // 建立“線程t2”, t2是基于demo這個Runnable對象
t1.start(); // 啟動“線程t1”
t2.start(); // 啟動“線程t2”
}
}
運作結果:
t1 loop 0
t1 loop 1
t1 loop 2
t1 loop 3
t1 loop 4
t2 loop 0
t2 loop 1
t2 loop 2
t2 loop 3
t2 loop 4
結果說明:
run()方法中存在“synchronized(this)代碼塊”,而且t1和t2都是基于"demo這個Runnable對象"建立的線程。這就意味着,我們可以将synchronized(this)中的this看作是“demo這個Runnable對象”;是以,線程t1和t2共享“demo對象的同步鎖”。是以,當一個線程運作的時候,另外一個線程必須等待“運作線程”釋放“demo的同步鎖”之後才能運作。
如果你确認,你搞清楚這個問題了。那我們将上面的代碼進行修改,然後再運作看看結果怎麼樣,看看你是否會迷糊。修改後的源碼如下:
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class Demo1_2 {
public static void main(String[] args) {
Thread t1 = new MyThread("t1"); // 建立“線程t1”
Thread t2 = new MyThread("t2"); // 建立“線程t2”
t1.start(); // 啟動“線程t1”
t2.start(); // 啟動“線程t2”
}
}
代碼說明:
比較Demo1_2 和 Demo1_1,我們發現,Demo1_2中的MyThread類是直接繼承于Thread,而且t1和t2都是MyThread子線程。
幸運的是,在“Demo1_2的run()方法”也調用了synchronized(this),正如“Demo1_1的run()方法”也調用了synchronized(this)一樣!
那麼,Demo1_2的執行流程是不是和Demo1_1一樣呢?
t1 loop 0
t2 loop 0
t1 loop 1
t2 loop 1
t1 loop 2
t2 loop 2
t1 loop 3
t2 loop 3
t1 loop 4
t2 loop 4
如果這個結果一點也不令你感到驚訝,那麼我相信你對synchronized和this的認識已經比較深刻了。否則的話,請繼續閱讀這裡的分析。
synchronized(this)中的this是指“目前的類對象”,即synchronized(this)所在的類對應的目前對象。它的作用是擷取“目前對象的同步鎖”。
對于Demo1_2中,synchronized(this)中的this代表的是MyThread對象,而t1和t2是兩個不同的MyThread對象,是以t1和t2在執行synchronized(this)時,擷取的是不同對象的同步鎖。對于Demo1_1對而言,synchronized(this)中的this代表的是MyRunable對象;t1和t2共同一個MyRunable對象,是以,一個線程擷取了對象的同步鎖,會造成另外一個線程等待。
第二條:
class Count {
// 含有synchronized同步塊的方法
public void synMethod() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
// 非同步的方法
public void nonSynMethod() {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
public class Demo2 {
public static void main(String[] args) {
final Count count = new Count();
// 建立t1, t1會調用“count對象”的synMethod()方法
Thread t1 = new Thread(
new Runnable() {
@Override
public void run() {
count.synMethod();
}
}, "t1");
// 建立t2, t2會調用“count對象”的nonSynMethod()方法
Thread t2 = new Thread(
new Runnable() {
@Override
public void run() {
count.nonSynMethod();
}
}, "t2");
t1.start(); // 啟動t1
t2.start(); // 啟動t2
}
}
t1 synMethod loop 0
t2 nonSynMethod loop 0
t1 synMethod loop 1
t2 nonSynMethod loop 1
t1 synMethod loop 2
t2 nonSynMethod loop 2
t1 synMethod loop 3
t2 nonSynMethod loop 3
t1 synMethod loop 4
t2 nonSynMethod loop 4
主線程中建立了兩個子線程t1和t2。t1會調用count對象的synMethod()方法,該方法内含有同步塊;而t2則會調用count對象的nonSynMethod()方法,該方法不是同步方法。t1運作時,雖然調用synchronized(this)擷取“count的同步鎖”;但是并沒有造成t2的阻塞,因為t2沒有用到“count”同步鎖。
第三條:
我們将上面的例子中的nonSynMethod()方法體的也用synchronized(this)修飾。修改後的源碼如下:
class Count {
// 含有synchronized同步塊的方法
public void synMethod() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100); // 休眠100ms
System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
// 也包含synchronized同步塊的方法
public void nonSynMethod() {
synchronized(this) {
try {
for (int i = 0; i < 5; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);
}
} catch (InterruptedException ie) {
}
}
}
}
public class Demo3 {
public static void main(String[] args) {
final Count count = new Count();
// 建立t1, t1會調用“count對象”的synMethod()方法
Thread t1 = new Thread(
new Runnable() {
@Override
public void run() {
count.synMethod();
}
}, "t1");
// 建立t2, t2會調用“count對象”的nonSynMethod()方法
Thread t2 = new Thread(
new Runnable() {
@Override
public void run() {
count.nonSynMethod();
}
}, "t2");
t1.start(); // 啟動t1
t2.start(); // 啟動t2
}
}
t1 synMethod loop 0
t1 synMethod loop 1
t1 synMethod loop 2
t1 synMethod loop 3
t1 synMethod loop 4
t2 nonSynMethod loop 0
t2 nonSynMethod loop 1
t2 nonSynMethod loop 2
t2 nonSynMethod loop 3
t2 nonSynMethod loop 4
主線程中建立了兩個子線程t1和t2。t1和t2運作時都調用synchronized(this),這個this是Count對象(count),而t1和t2共用count。是以,在t1運作時,t2會被阻塞,等待t1運作釋放“count對象的同步鎖”,t2才能運作。
synchronized方法和synchronized代碼塊
"synchronized方法"是用synchronized修飾方法,而"synchronized代碼塊"則是用synchronized修飾代碼塊。
synchronized方法示例
public synchronized void foo1() {
System.out.println("synchronized methoed");
}
synchronized代碼塊
public void foo2() {
synchronized (this) {
System.out.println("synchronized methoed");
}
}
synchronized代碼塊中的this是指目前對象。也可以将this替換成其他對象,例如将this替換成obj,則foo2()在執行synchronized(obj)時就擷取的是obj的同步鎖。
synchronized代碼塊可以更精确的控制沖突限制通路區域,有時候表現更高效率。下面通過一個示例來示範:
// Demo4.java的源碼
public class Demo4 {
public synchronized void synMethod() {
for(int i=0; i<1000000; i++)
;
}
public void synBlock() {
synchronized( this ) {
for(int i=0; i<1000000; i++)
;
}
}
public static void main(String[] args) {
Demo4 demo = new Demo4();
long start, diff;
start = System.currentTimeMillis(); // 擷取目前時間(millis)
demo.synMethod(); // 調用“synchronized方法”
diff = System.currentTimeMillis() - start; // 擷取“時間內插補點”
System.out.println("synMethod() : "+ diff);
start = System.currentTimeMillis(); // 擷取目前時間(millis)
demo.synBlock(); // 調用“synchronized方法塊”
diff = System.currentTimeMillis() - start; // 擷取“時間內插補點”
System.out.println("synBlock() : "+ diff);
}
}
(某一次)執行結果:
synMethod() : 11
synBlock() : 3
原文釋出時間為:2018-07-05
本文作者:skywang12345
本文來自雲栖社群合作夥伴“
Java架構師之路”,了解相關資訊可以關注“
”。
社群技術交流:歡迎關注【
阿裡Java技術進階】每周在群内進行【免費技術直播】和【線上回答技術問題】歡迎點選link入群:
http://tb.cn/gXRstIw或者 釘釘掃碼入群:
