[Java] Synchronize in Java

在了解Java的synchronized機制之前,先來複習一下Monitor。

可以把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使用方式可以分為兩種:

  1. synchronized method
  2. 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
23
public 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
20
public class Thread1 implements Runnable {
private TargetObject targetObject;

public Thread1(TargetObject targetObject) {
this.targetObject = targetObject;
}

@Override
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
20
public class Thread2 implements Runnable {
private TargetObject targetObject;

public Thread1(TargetObject targetObject) {
this.targetObject = targetObject;
}

@Override
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
9
public 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
6
Thread1 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.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public 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
9
Thread1 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.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Thread1 implements Runnable {
private TargetObject targetObject;

public Thread1(TargetObject targetObject) {
this.targetObject = targetObject;
}

@Override
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
9
Thread1 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
2
3
synchronized(想要鎖定的物件或是class literal){
//do something
}

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
24
public class Thread1 implements Runnable {
private TargetObject targetObject;
private byte[] lock;

public Thread1(TargetObject targetObject, byte[] lock) {
this.targetObject = targetObject;
this.lock = lock;
}

@Override
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
24
public class Thread2 implements Runnable {
private TargetObject targetObject;
private byte[] lock;

public Thread2(TargetObject targetObject, byte[] lock) {
this.targetObject = targetObject;
this.lock = lock;
}

@Override
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
11
public 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的時候,務必要搞清楚鎖定的對象,沒有搞清楚反而等同於沒有同步。