在了解Java的synchronized機制之前,先來複習一下Monitor。
可以把Monitor想像成它就是一個類別,裡面存放著共享變數、方法和建構子。
Monitor會確保只有一個Process可以進入,其他也想要進入的Process,就必須在queue裡面等待。程式設計師可以根據不同的情況,決定是否要讓正在Monitor的Process進入waiting state或是喚醒其他在waiting state的Process。
例如:當某個Process取得Monitor的執行權利,在執行過程中發現不符合x情況,必須進入waiting state,可以使用x.wait()。想要喚醒x queue中的waiting Process,可以透過x.signal()。詳細可以參考Operating System Concepts。
Java的synchronized就是實作了Monitor,Object的wait()和notify()就相當於wait()和signal()。有了這樣的概念後,在使用synchronized就比較不會發生鎖錯對象的問題。
synchronized使用方式可以分為兩種:
- synchronized method
- synchronized block
範例
TargetObject.java:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class TargetObject {
private static int count = 0;
public void method1() {
count++;
System.out.println(Thread.currentThread().getName() + " in method1 and count = " + count);
}
public void method2() {
count++;
System.out.println(Thread.currentThread().getName() + " in method2 and count = " + count);
}
public static void staticMethod1() {
count++;
System.out.println(Thread.currentThread().getName() + " in static method1 and count = " + count);
}
public static void staticMethod2() {
count++;
System.out.println(Thread.currentThread().getName() + " in static method2 and count = " + count);
}
}
Thread1.java:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Thread1 implements Runnable {
private TargetObject targetObject;
public Thread1(TargetObject targetObject) {
this.targetObject = targetObject;
}
public void run() {
while (true) {
this.targetObject.method1();
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread2.java:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Thread2 implements Runnable {
private TargetObject targetObject;
public Thread1(TargetObject targetObject) {
this.targetObject = targetObject;
}
public void run() {
while (true) {
this.targetObject.method2();
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Run.java:1
2
3
4
5
6
7
8
9public class Run {
public static void main(String[] args) {
TargetObject targetObject = new TargetObject();
Thread thread1 = new Thread(new Thread1(targetObject), "Thread1");
Thread thread2 = new Thread(new Thread2(targetObject), "Thread2");
thread1.start();
thread2.start();
}
}
執行結果:沒有做同步處理,會存在race condition。1
2
3
4
5
6Thread1 in method1 and count = 2
Thread2 in method2 and count = 2
Thread2 in method2 and count = 3
Thread1 in method1 and count = 4
Thread2 in method2 and count = 5
Thread2 in method2 and count = 6
synchronized method
在TargetObject.java中的method1和method2加上 synchronized
就可以鎖定由TargetObject類別所實體化的物件targetObject。
TargetObject.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public class TargetObject {
private static int count = 0;
public synchronized void method1() {
count++;
System.out.println(Thread.currentThread().getName() + " in method1 and count = " + count);
}
public synchronized void method2() {
count++;
System.out.println(Thread.currentThread().getName() + " in method2 and count = " + count);
}
public static void staticMethod1() {
count++;
System.out.println(Thread.currentThread().getName() + " in static method1 and count = " + count);
}
public static void staticMethod2() {
count++;
System.out.println(Thread.currentThread().getName() + " in static method2 and count = " + count);
}
}
執行結果:1
2
3
4
5
6
7
8
9Thread1 in method1 and count = 1
Thread2 in method2 and count = 2
Thread2 in method2 and count = 3
Thread1 in method1 and count = 4
Thread2 in method2 and count = 5
Thread2 in method2 and count = 6
Thread1 in method1 and count = 7
Thread2 in method2 and count = 8
Thread2 in method2 and count = 9
為了更清楚了解鎖定的對象,現在先 移除 method1的synchronized,將同步機制移到Thread1.java。
Thread1.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class Thread1 implements Runnable {
private TargetObject targetObject;
public Thread1(TargetObject targetObject) {
this.targetObject = targetObject;
}
public void run() {
while (true) {
synchronized (targetObject) {
this.targetObject.method1();
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
執行結果:1
2
3
4
5
6
7
8
9Thread1 in method1 and count = 1
Thread2 in method2 and count = 2
Thread2 in method2 and count = 3
Thread1 in method1 and count = 4
Thread2 in method2 and count = 5
Thread2 in method2 and count = 6
Thread1 in method1 and count = 7
Thread2 in method2 and count = 8
Thread2 in method2 and count = 9
從執行結果可以看出上鎖的對象是在Run.java中實體化的 targetObject。
若將
- Thread1.java中的
this.targetObject.method1()
改成TargetObject.staticMethod1()
。 - Thread2.java中的
this.targetObject.method2()
改成TargetObject.staticMethod2()
。
必須對TargetObject的staticMethod1和staticMethod2做 synchronized
。
透過上述的方式可以知道synchronized static method是鎖定 TargetObject.class
。
synchronized block
1 | synchronized(想要鎖定的物件或是class literal){ |
synchronized block較有彈性,可以選擇鎖定的對象。
Thread1.java:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class Thread1 implements Runnable {
private TargetObject targetObject;
private byte[] lock;
public Thread1(TargetObject targetObject, byte[] lock) {
this.targetObject = targetObject;
this.lock = lock;
}
public void run() {
while (true) {
synchronized (this.lock) {
this.targetObject.method1();
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Thread2.java:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class Thread2 implements Runnable {
private TargetObject targetObject;
private byte[] lock;
public Thread2(TargetObject targetObject, byte[] lock) {
this.targetObject = targetObject;
this.lock = lock;
}
public void run() {
while (true) {
synchronized (this.lock) {
this.targetObject.method2();
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Run.java:1
2
3
4
5
6
7
8
9
10
11public class Run {
public static void main(String[] args) {
TargetObject targetObject = new TargetObject();
final byte[] lock = new byte[0];
Thread thread1 = new Thread(new Thread1(targetObject, lock), "Thread1");
Thread thread2 = new Thread(new Thread2(targetObject, lock), "Thread2");
thread1.start();
thread2.start();
}
}
在使用synchronized的時候,務必要搞清楚鎖定的對象,沒有搞清楚反而等同於沒有同步。