重入锁与条件对象
synchronized 关键字自动提供了锁以及相关的条件。大多数需要显 式锁的情况使用synchronized非常方便,但是等我们了解了重入锁和条 件对象时,能更好地理解synchronized关键字。重入锁ReentrantLock是 Java SE 5.0引入的,就是支持重进入的锁,它表示该锁能够支持一个线 程对资源的重复加锁。
这一结构确保任何时刻只有一个线程进入临界区,临界区就是在同
一时刻只能有一个任务访问的代码区。一旦一个线程封锁了锁对象,其 他任何线程都无法进入Lock语句。把解锁的操作放在finally中是十分必 要的。如果在临界区发生了异常,锁是必须要释放的,否则其他线程将 会永远被阻塞。进入临界区时,却发现在某一个条件满足之后,它才能 执行。这时可以使用一个条件对象来管理那些已经获得了一个锁但是却 不能做有用工作的线程,条件对象又被称作条件变量
注:一旦一个线程调用 await 方法,它就会进入该条件的等待集并处于 阻塞状态,直到另一个线程调用了同一个条件的signalAll方法时为止。
同步方法
Lock和 Condition接口为程序设计人员提供了高度的锁定控制,然 而大多数情况下,并不需要那样的控制,并且可以使用一种嵌入到Java 语言内部的机制。从Java 1.0版开始,Java中的每一个对象都有一个内部 锁。如果一个方法用 synchronized 关键字声明,那么对象的锁将保护整 个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。
我们可以将Alipay类的transfer方法声 明为synchronized,而不是使用一个显式的锁。内部对象锁只有一个相 关条件,wait方法将一个线程添加到等待集中,notifyAll或者notify方法 解除等待线程的阻塞状态。也就是说wait相当于调用 condition.await(),notifyAll等价于condition.signalAll()
同步代码块
还有另一种机制可以获得锁,那就是使用一个同步代码 块
synchronize(obj){
}
其获得了obj的锁,obj指的是一个对象
一般实现同步最 好用java.util.concurrent包下提供的类,比如阻塞队列。如果同步方法适 合你的程序,那么请尽量使用同步方法,这样可以减少编写代码的数 量,减少出错的概率。如果特别需要使用Lock/Condition结构提供的独 有特性时,才使用Lock/Condition。
volatile
volatile关键字为实例域的同步访问提供了免锁的机制。如果声 明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线 程并发更新的
volatile关键字
当一个共享变量被volatile修饰之后,其就具备了两个含义,一个是 线程修改了变量的值时,变量的新值对其他线程是立即可见的。换句话 说,就是不同线程对这个变量进行操作时具有可见性。另一个含义是禁 止使用指令重排序。
注:那么什么是重排序呢?重排序通常是编译器或 运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种 手段。重排序分为两类:编译期重排序和运行期重排序,分别对应编译 时和运行时环境。
volatile不保证原子性
volatile保证有序性
volatile关键字能禁止指令重排序,因此volatile能保证有序性。 volatile关键字禁止指令重排序有两个含义:一个是当程序执行到volatile 变量的操作时,在其前面的操作已经全部执行完毕,并且结果会对后面 的操作可见,在其后面的操作还没有进行;在进行指令优化时,在 volatile变量之前的语句不能在volatile变量后面执行;同样,在volatile变 量之后的语句也不能在volatile变量前面执行。
正确使用volatile关键字
synchronized关键字可防止多个线程同时执行一段代码,那么这就 会很影响程序执行效率。而volatile关键字在某些情况下的性能要优于 synchronized。但是要注意volatile关键字是无法替代synchronized关键字 的,因为volatile关键字无法保证操作的原子性。通常来说,使用volatile 必须具备以下两个条件: (1)对变量的写操作不会依赖于当前值。 (2)该变量没有包含在具有其他变量的不变式中。 第一个条件就是不能是自增、自减等操作,上文已经提到volatile不 保证原子性。关于第二个条件,我们来举一个例子,它包含了一个不变 式:下界总是小于或等于上界
总结
与锁相比,volatile变量是一种非常简单但同时又非常脆弱的同步机 制,它在某些情况下将提供优于锁的性能和伸缩性。如果严格遵循 volatile的使用条件,即变量真正独立于其他变量和自己以前的值,在某 些情况下可以使用volatile代替synchronized来简化代码。然而,使用 volatile的代码往往比使用锁的代码更加容易出错。在前面的第 4 小节中 介绍了可以使用 volatile 代替synchronized的最常见的两种用例,在其他 情况下我们最好还是使用synchronized。