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操作是对同一个实例的监视器进行的操作。
同步处理
synchronized(lock/unlock操作)并不仅仅进行线程的互斥处理。Java内存模型确保了某个线程在进行unlock M操作前进行的所有写入操作对进行lock M操作的线程都是可见的。
1 | // 不可能显示出x<y |
在进行如下处理时,线程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是可见的。