Java中Volatile变量的使用
Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性
volatile 变量不能用作线程安全计数器
线程能够自动发现 volatile 变量的最新值
Volatile 变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束
单独使用 volatile 还不足以实现计数器、互斥锁或任何具有与多个变量相关的不变式(Invariants)的类
使用条件
对变量的写操作不依赖于当前值。
该变量没有包含在具有其他变量的不变式中。
即只有在状态真正独立于程序内其他内容时才能使用 volatile 然而,使用 volatile 的代码往往比使用锁的代码更加容易出错
性能 使用 volatile 变量的主要原因是其简易性:在某些情形下,使用 volatile 变量要比使用相应的锁简单得多。 使用 volatile 变量次要原因是其性能:某些情况下,volatile 变量同步机制的性能要优于锁。 volatile 变量不会像锁那样造成线程阻塞,因此也很少造成可伸缩性问题。 在某些情况下,如果读操作远远大于写操作,volatile 变量还可以提供优于锁的性能优势
使用模式
- 状态标志(boolean): 通常只有一种状态转换
volatile boolean shutdownRequested;
...
...
public void shutdown() { shutdownRequested = true; }
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}
一次性安全发布 (one-time safe publication) 被发布的对象必须是线程安全的,或者是有效的不可变对象(有效不可变意味着对象的状态在发布之后永远不会被修改)
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); } } }
独立观察(independent observation) 定期 “发布” 观察结果供程序内部使用
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” 模式 a. 在 volatile bean 模式中,JavaBean 被用作一组具有 getter 和/或 setter 方法 的独立属性的容器。 volatile bean 模式的基本原理是:很多框架为易变数据的持有者(例如 HttpSession)提供了容器, 但是放入这些容器中的对象必须是线程安全的。
b. 在 volatile bean 模式中,JavaBean 的所有数据成员都是 volatile 类型的, 并且 getter 和 setter 方法必须非常普通 —— 除了获取或设置相应的属性外,不能包含任何逻辑 (这将禁止具有数组值的属性)
c. 对于任何 volatile 变量,不变式或约束都不能包含 JavaBean 属性
```` @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 变量来减少公共代码路径的开销 锁一次只允许一个线程访问值,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++; } }
相关文章
- 基于-SLF4J-MDC-机制的日志链路追踪配置属性
ums: # ================ 基于 SLF4J MDC 机制的日志链路追踪配置属性 ================ mdc: # 是否支持基于 SLF4J MDC
- ajax-跨域访问
ajax 跨域访问 <!DOCTYPE html> <html xmlns:th="http://www.w3.org/1999/xhtml"> <head>
- 给第三方登录时用的数据库表-user_connection-与-auth_token-添加-redis-cache
spring: # 设置缓存为 Redis cache: type: redis # redis redis: host: 192.168.88.88 port
- Java动态代理
Jdk动态代理 通过InvocationHandler和Proxy针对实现了接口的类进行动态代理,即必须有相应的接口 应用 public class TestProxy { public
- Java读取classpath中的文件
public void init() { try { //URL url = Thread.currentThread().getContextClassLo
随机推荐
- 基于-SLF4J-MDC-机制的日志链路追踪配置属性
ums: # ================ 基于 SLF4J MDC 机制的日志链路追踪配置属性 ================ mdc: # 是否支持基于 SLF4J MDC
- ajax-跨域访问
ajax 跨域访问 <!DOCTYPE html> <html xmlns:th="http://www.w3.org/1999/xhtml"> <head>
- 给第三方登录时用的数据库表-user_connection-与-auth_token-添加-redis-cache
spring: # 设置缓存为 Redis cache: type: redis # redis redis: host: 192.168.88.88 port
- Java动态代理
Jdk动态代理 通过InvocationHandler和Proxy针对实现了接口的类进行动态代理,即必须有相应的接口 应用 public class TestProxy { public
- Java读取classpath中的文件
public void init() { try { //URL url = Thread.currentThread().getContextClassLo