参考:Java多线程编程 实战指南 核心篇

1、线程同步机制介绍

第2章介绍导致线程安全问题的因素时更多的是侧重其根源,包括硬件、和软件编译器。但是从应用程序的角度来看,线程安全问题的产生是由于多线程应用程序缺乏某种东西 ————线程同步机制。
线程同步机制是一套用于协调线程间的数据访问(Data access)及活动(Activity)的机制,该机制用于保障线程安全以及实现以及实现这些线程的共同目标。如果把线程比作在公路上行驶的车辆,那边线程同步机制就好比是任何车辆都需要遵守的交通规则。公路上行驶的车辆只有遵守交通规则才能够到达目的地 ——安全地到达目的地
从广义上讲,Java平台提供的线程同步机制包括 锁、volatile关键字、final关键字、static关键字以及一些相关的API,如 Object.wait()/Object.notify()等。本章介绍的是Java平台中用于协调线程间共享数据访问的相关关键字和API。

2、锁的概述

我们知道线程安全问题的产生前提是多个线程并发访问共享变量、共享资源(以下统称为共享数据)。于是,我们很容易想到一种保障线程安全的方法———将多个线程对共享数据的并发访问转换为串行访问,即一个共享数据一次只能被一个线程访问,该线程访问结束后其他线程才能对其进行访问。锁(Lock)就是利用这种思想以保证线程安全同步机制。
按照上述思路,锁可以理解为对共享数据进行保护的许可证。对于同一个许可证所保护的共享数据而言,任何线程访问这些共享数据前必须先持有该许可证。一个线程只有在持有许可证的情况下才能够对这些共享数据进行访问;并且,一个许可证一次只能够被一个线程持有;许可证的持有线程在其结束对共享数据的访问后必须让出(释放)其持有的许可证,以便其他线程能够对这些共享数据进行访问。
锁的持有线程在其获得锁之后和释放锁之前这段时间内所执行的代码被称为临界区。因此,共享数据只允许在临界区内进行访问,临界区一次只能被一个线程执行。

按照Java 虚拟机对锁的实现方式划分,Java平台中的锁包括 内部锁 和 显示锁。
内部锁是通过synchronized关键字实现的;
显示锁是通过java.concurrent.locks.Lock接口的实现类实现的;

1、Java线程同步机制 - 图1

3、锁的作用

锁能够保护共享数据以实现线程安全,其作用包括保障原子性、保障可见性、保障有序性。
锁是通过互斥保障原子性的。
可见性的保障是通过写线程冲刷处理器缓存和读线程刷新处理器缓存这两个动作实现的。在Java平台中,锁的获得隐含着刷新处理器缓存这个动作,这使得读线程在执行临界区代码前(获得锁之后)可以将写线程对共享变量所做的更新同步到该线程执行处理器的高速缓存中;而锁的释放隐含着冲刷处理器缓存这个动作,这使得写线程对共享变量所做的更新能够被”推送“到该线程执行处理器的高速缓存中,从而对读线程可同步。因此锁能够保障可见性。
锁能够保障有序性,