首先,这里不是赘述volatile关键字的原理,主要讨论的是“为什么volatile线程不安全,却仍要使用?”,这个问题是之前我在学习volatile关键字时困扰的。
同步机制
- synchronized
- volatile
第一种synchronized同步代码块,肯定能达到线程安全,但是性能较低;相比较而言,第二种,volatile就比较轻量级、简单、开销低、不会线程阻塞,但同时,也没办法完全保障线程安全,不具备原子性。
volatile - 使用条件
- 多个变量之间,或某个变量的当前值和修改后的值之间没有约束。
- 在某些情况下,如果读操作远远大于写操作,volatile变量可以提供优于锁的性能。
- 应用条件:
- 对变量的写操作,不依赖于当前值;--->不能用作线程安全计数器
- 该变量不包含在具有其他变量的不变式中。
volatile - 适用场景
- 状态标志,比如Boolean。
用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。volatile boolean shutdownRequested; ... public void shutdown() { shutdownRequested = true; } public void doWork() { while (!shutdownRequested) { // do stuff } }
- 一次性安全发布
将对象引用定义为 volatile 类型。后台线程在启动阶段从数据库加载一些数据。其他代码在能够利用这些数据时,在使用之前将检查这些数据是否曾经发布过。public class BackgroundFloobleLoader { public volatile Flooble theFlooble; public void initInBackground() { // do lots of stuff theFlooble = new Flooble(); // this is the only write to theFlooble } } public class SomeOtherClass { public void doWork() { while (true) { // do some stuff... // use the Flooble, but only if it is ready if (floobleLoader.theFlooble != null) doSomething(floobleLoader.theFlooble); } } }
- 独立观察
定期 “发布” 观察结果供程序内部使用。
例如,假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。public class UserManager { public volatile String lastUser; public boolean authenticate(String user, String password) { boolean valid = passwordIsValid(user, password); if (valid) { User u = new User(); activeUsers.add(u); lastUser = user; } return valid; } }
- “volatile bean” 模式
JavaBean 被用作一组具有 getter 和/或 setter 方法 的独立属性的容器。volatile bean 模式的基本原理是:很多框架为易变数据的持有者(例如HttpSession
)提供了容器,但是放入这些容器中的对象必须是线程安全的。
在 volatile bean 模式中,JavaBean 的所有数据成员都是 volatile 类型的,并且 getter 和 setter 方法必须非常普通。@ThreadSafe public class Person { private volatile String firstName; private volatile String lastName; private volatile int age; public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public int getAge() { return age; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setAge(int age) { this.age = age; } }
- 开销较低的读-写锁策略
如果读操作远远超过写操作,您可以结合使用内部锁和 volatile 变量来减少公共代码路径的开销。@ThreadSafe public class CheesyCounter { // Employs the cheap read-write lock trick // All mutative operations MUST be done with the 'this' lock held @GuardedBy("this") private volatile int value; public int getValue() { return value; } public synchronized int increment() { return value++; } }