上文中說起在java中能夠使用synchronized
關鍵字來解決竟態條件。主要經過synchronized
關鍵字來標註代碼塊,告訴jvm該代碼塊爲臨界區代碼,以保證每次只會有一個線程能訪問到該代碼塊裏的代碼,直到一個線程執行完畢後,另外一個線程才能執行。html
synchronized
使用對象做爲同步鎖。若多個同步代碼塊使用的是同一個對象鎖,那麼一次只能有一個線程訪問多個同步代碼塊中的一個。若多個同步代碼塊使用的不是同一個對象鎖,那麼多個線程可以同時訪問多個同步代碼塊。java
synchronized
一共有四種用法,以下所示:安全
在方法簽名中聲明synchronized
,可以讓整個方法標註爲代碼塊。多線程
public synchronized void method0() {
// do something
}
複製代碼
示例代碼中使用的是該方法所屬的實例對象做爲對象鎖。併發
public synchronized static void method0() {
// do something
}
複製代碼
示例代碼中使用的是該方法所屬類聲明指向的靜態實例對象做爲對象鎖。jvm
若不但願將整個方法標註爲代碼塊,能夠在方法中標註部分代碼塊做爲同步代碼塊。工具
public void method1() {
synchronized (this) {
// do something
}
}
複製代碼
在實例方法中,經過synchronized
構造方法的方式來標註代碼塊,括號中傳遞的是該同步代碼塊使用的對象鎖,須要實現同步的臨界區代碼寫在{}
中。代碼中的this
指該代碼塊所屬方法所屬的對象實例做爲對象鎖。該方式與在實例方法簽名中聲明synchronized
效果至關。post
public synchronized static void method1() {
synchronized (MyClass.class) {
// do something
}
}
複製代碼
在靜態方法中,經過synchronized
構造方法的方式來標註代碼塊,括號中傳遞的是該同步代碼塊使用的對象鎖,須要實現同步的臨界區代碼寫在{}
中。代碼中的MyClass.class
指該代碼塊所屬方法所屬類的靜態對象實例做爲對象鎖。該方式與在靜態方法簽名中聲明synchronized
效果至關。this
synchronized
編碼實例,咱們在SynchronizedExample中編寫四個方法,分別反映上文說起的四種狀況。每一個方法中都在線程進入後暫停3s,隨後線程退出代碼塊。編碼
public class SynchronizedExample {
private static void method(String name) {
final Thread thread = Thread.currentThread();
LocalDateTime now = LocalDateTime.now();
System.out.println(thread.getName() + ": [" + now + "] in " + name);
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void method0() {
method("instance-method0");
}
public void method1() {
synchronized (this) {
method("instance-method1");
}
}
public synchronized static void method2() {
method("static-method2");
}
public synchronized static void method3() {
synchronized (SynchronizedExample.class) {
method("static-method3");
}
}
public static void main(String[] args) {
SynchronizedExample example = new SynchronizedExample();
Runnable myRunnable0 = () -> {
example.method0();
example.method1();
};
Runnable myRunnable1 = () -> {
SynchronizedExample.method2();
SynchronizedExample.method3();
};
IntStream.range(1, 3)
.forEach(i -> new Thread(myRunnable0, "Thread-" + i).start());
// 實例同步方法須要12s才能執行完,主線程等待13s後再執行靜態同步方法
try {
Thread.sleep(13000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
IntStream.range(1, 3)
.forEach(i -> new Thread(myRunnable1, "Thread-" + i).start());
}
}
複製代碼
執行結果:
Thread-1: [2019-03-15T14:48:50.251] in instance-method0
Thread-2: [2019-03-15T14:48:53.255] in instance-method0
Thread-2: [2019-03-15T14:48:56.258] in instance-method1
Thread-1: [2019-03-15T14:48:59.262] in instance-method1
Thread-1: [2019-03-15T14:49:03.234] in static-method2
Thread-2: [2019-03-15T14:49:06.238] in static-method2
Thread-2: [2019-03-15T14:49:09.243] in static-method3
Thread-1: [2019-03-15T14:49:12.247] in static-method3
從結果能夠看出,使用同個對象實例做爲對象鎖和使用同個靜態對象做爲對象鎖的方法分別被線程1和線程2訪問。 從上文打印的時間能夠看出每一個線程每次僅能訪問使用同個對象鎖的多個同步代碼塊中的一個。每3s執行完一個同步代碼塊。
實際上synchronized
是java中第一次針對競態條件發佈的同步措施,但在實際開發中並非那麼好用,所以在jdk1.5後,發佈了整個併發工具包
,提供了各式各樣的多線程安全控件,用於協助開發者編寫線程安全的應用程序。
該系列博文爲筆者複習基礎所著譯文或理解後的產物,複習原文來自Jakob Jenkov所著Java Concurrency and Multithreading Tutorial