synchronized

synchronized具有“线程的互斥处理”和“同步处理”两种功能。

线程的互斥处理

如果程序中有synchronized关键字,线程就会进行lock/unlock操作。线程会在synchronized开始时获取锁(lock),在synchronized终止时释放锁(unlock)。

进行lock/unlock的部分并不仅仅是程序中写有synchronized的部分。当线程在wait方法内部等待的时候也会释放锁。此外,当线程从wait方法中出来的时候还必须先重新获取锁后才能继续运行。

只有一个线程能够获取某个实例的锁。因此,当线程A正准备获取锁时,如果其他线程已经获取了锁,那么线程A就会进入等待队列(或入口队列)。这样就实现了线程的互斥(mutal exclusion)。

synchronized的互斥处理如下图所示。这幅图展示了当线程A执行了unlock操作但是还没有从中出来时,线程B就无法执行lock操作的情形。图中的unlock M和lock M中都写了一个M,这表示unlock操作和lock操作是对同一个实例的监视器进行的操作。

kdFXKU.png

同步处理

synchronized(lock/unlock操作)并不仅仅进行线程的互斥处理。Java内存模型确保了某个线程在进行unlock M操作前进行的所有写入操作对进行lock M操作的线程都是可见的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 不可能显示出x<y
public class Main {

public static void main(String[] args) {
final Something obj = new Something();

new Thread() {
@Override
public void run() {
obj.write();
}
}.start();

new Thread() {
@Override
public void run() {
obj.read();
}
}.start();
}
}

class Something {
private int x = 0;
private int y = 0;

public synchronized void write() {
x = 100;
y = 50;
}

public synchronized void read() {
if (x < y) {
System.out.println("x < y");
}
}
}

kdAy1P.png

在进行如下处理时,线程A的写操作对线程B是可见的:

  • 线程A对字段x和y写值(normal write操作)
  • 线程A进行unlock操作
  • 线程B对同一个监视器M进行lock操作
  • 线程B读取字段x和y的值(normal read)

大体来说就是,

  • 进行unlock操作后,写入缓存中的内容会被强制地写入共享内存中
  • 进行lock操作后,缓存中的内容会先失效,然后共享内存中的最新内容会被强制重新读取到缓存中

代码不可能显示出x<y的原因有以下两个:

(1) 互斥处理可以防止read方法中断write方法的处理。虽然在write方法内部会发生重排序,但是该重排序不会对read方法产生任何影响。
(2) 同步处理可以确保write方法向字段x、y写入的值对运行read方法的线程B是可见的。