修饰符volatile学习笔记
这份代码说明了两个问题:1、线程间共享的变量会拷贝到线程的工作内存,如果不能及时写回到主存,将造成线程间共享变量不同步;2、加上volatile修饰符使得线程间共享变量同步会引起a == !a之类的逻辑错误,这是因为我们没有对非原子性操作boolValue == !boolValue加锁。所以可以修改这份代码来修正这两个错误:
Java代码
public class VolatileObjectTest {
public static void main(String[] args) {
final VolatileObjectTest volObj=new VolatileObjectTest();
Thread t2=new Thread(){
public void run(){
System.out.println("t1 start");
for(;;){
volObj.waitToExit();
}
}
};
t2.start();
Thread t1=new Thread(){
public void run(){
System.out.println("t2 start");
for(;;){
volObj.swap();
}
}
};
t1.start();
}
volatile boolean boolValue;
// 加锁
public synchronized void waitToExit() {
if(boolValue == !boolValue) {
System.out.println("exit...");
System.exit(0);
}
}
// 加锁
public synchronized void swap() {
boolValue = !boolValue;
}
}
问题一:用java.util.concurrent.atomic.AtomicBoolean来替代boolean,达到上面这份代码的效果。
第一篇文章中的代码很好的演示了volatile修饰符的作用和局限:保证共享变量在线程间的可见性,无法保证共享变量的非原子操作的互斥性。第二篇文章Java theory and practice: Managing volatility进一步展示如何正确使用volatile修饰符。
正确使用volatile需要满足两个条件:
? Writes to the variable do not depend on its current value.
? The variable does not participate in invariants with other variables.
这两个条件其实都是针对变量非原子性操作提出来的,第一个条件表明写volatile变量不能依赖其当前值,比如i++,i+=i之类的操作,因为这些操作涉及“读-改-写”一系列动作;第二个条件表明volatile变量不能与其他变量共同决定某个状态,比如对上下限(a,b)中的上限和下限分别赋值,这可能导致下限大于上限。
引入volatile修饰符的原因有两个:使用简单,效率高,如果能够在恰当的时机使用它对编写高效的代码很有益,有五种模式:
1. Status flags
Java代码
volatile boolean shutdownRequested;
...
public void shutdown() { shutdownRequested = true; }
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}
在多线的情况下,某个线程调用shutdown函数,volatile修饰符保证变量shutdownRequested的更新值能够被及时写入主存供其他线程调用。
2. Onetime safe publication
Java代码
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);
}
}
}
这个模式的用法与Double-Checked Locking的问题有关,详细描述请见The "Double-Checked Locking is Broken" Declaration。在执行“theFlooble = new Flooble()”时,虚拟机可能会对构造函数和赋值语句的执行序列进行重组优化,也就是说先为Flooble的实例分配内存,并将这个内存地址赋予theFlooble,再调用Flooble的构造函数。为对象引用theFlooble加上volatile修饰符能保证虚拟机在调用Flooble的构造函数之后才将其地址赋予theFlooble,其他线程就能操作被成功构造的对象。
值得注意的是,这个模式有一个限制条件, initInBackground语义表明希望其他线程操作initInBackground之后的对象,initInBackground函数中如果还有其他更改theFlooble对象成员变量的操作将违反volatile修饰符的使用条件一:非原子性操作。
3. Independent observations
Java代码
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;
}
}
这其实是模式二的一个延伸。
4. The “volatile bean” pattern
Java代码
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;
}
}
这个模式的名字暗指类似于bean,变量的setter和getter加上volatile修饰符可以保证线程安全,这些getter,setter不能包含对变量的逻辑操作,假如被修饰的是对象引用,所引用的对象的成员变量必须是一些不变值,因为volatile修饰的是引用本身,对引用的对象并不起作用。
5. The cheap read-write lock trick
Java代码
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++;
}
}
这个模式是对那些非原子性操作加上同步锁,而原子性操作则保留了volatile变量的效率。
整篇文章在教我们如何正确使用volatile,实际上还是颇多陷阱,这也是引入java.util.concurrent.atomic包的原因。比如执行getAndDecrement这类操作,使用atomic包远比模式五要简单。
这份代码说明了两个问题:1、线程间共享的变量会拷贝到线程的工作内存,如果不能及时写回到主存,将造成线程间共享变量不同步;2、加上volatile修饰符使得线程间共享变量同步会引起a == !a之类的逻辑错误,这是因为我们没有对非原子性操作boolValue == !boolValue加锁。所以可以修改这份代码来修正这两个错误:
Java代码
public class VolatileObjectTest {
public static void main(String[] args) {
final VolatileObjectTest volObj=new VolatileObjectTest();
Thread t2=new Thread(){
public void run(){
System.out.println("t1 start");
for(;;){
volObj.waitToExit();
}
}
};
t2.start();
Thread t1=new Thread(){
public void run(){
System.out.println("t2 start");
for(;;){
volObj.swap();
}
}
};
t1.start();
}
volatile boolean boolValue;
// 加锁
public synchronized void waitToExit() {
if(boolValue == !boolValue) {
System.out.println("exit...");
System.exit(0);
}
}
// 加锁
public synchronized void swap() {
boolValue = !boolValue;
}
}
问题一:用java.util.concurrent.atomic.AtomicBoolean来替代boolean,达到上面这份代码的效果。
第一篇文章中的代码很好的演示了volatile修饰符的作用和局限:保证共享变量在线程间的可见性,无法保证共享变量的非原子操作的互斥性。第二篇文章Java theory and practice: Managing volatility进一步展示如何正确使用volatile修饰符。
正确使用volatile需要满足两个条件:
? Writes to the variable do not depend on its current value.
? The variable does not participate in invariants with other variables.
这两个条件其实都是针对变量非原子性操作提出来的,第一个条件表明写volatile变量不能依赖其当前值,比如i++,i+=i之类的操作,因为这些操作涉及“读-改-写”一系列动作;第二个条件表明volatile变量不能与其他变量共同决定某个状态,比如对上下限(a,b)中的上限和下限分别赋值,这可能导致下限大于上限。
引入volatile修饰符的原因有两个:使用简单,效率高,如果能够在恰当的时机使用它对编写高效的代码很有益,有五种模式:
1. Status flags
Java代码
volatile boolean shutdownRequested;
...
public void shutdown() { shutdownRequested = true; }
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}
在多线的情况下,某个线程调用shutdown函数,volatile修饰符保证变量shutdownRequested的更新值能够被及时写入主存供其他线程调用。
2. Onetime safe publication
Java代码
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);
}
}
}
这个模式的用法与Double-Checked Locking的问题有关,详细描述请见The "Double-Checked Locking is Broken" Declaration。在执行“theFlooble = new Flooble()”时,虚拟机可能会对构造函数和赋值语句的执行序列进行重组优化,也就是说先为Flooble的实例分配内存,并将这个内存地址赋予theFlooble,再调用Flooble的构造函数。为对象引用theFlooble加上volatile修饰符能保证虚拟机在调用Flooble的构造函数之后才将其地址赋予theFlooble,其他线程就能操作被成功构造的对象。
值得注意的是,这个模式有一个限制条件, initInBackground语义表明希望其他线程操作initInBackground之后的对象,initInBackground函数中如果还有其他更改theFlooble对象成员变量的操作将违反volatile修饰符的使用条件一:非原子性操作。
3. Independent observations
Java代码
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;
}
}
这其实是模式二的一个延伸。
4. The “volatile bean” pattern
Java代码
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;
}
}
这个模式的名字暗指类似于bean,变量的setter和getter加上volatile修饰符可以保证线程安全,这些getter,setter不能包含对变量的逻辑操作,假如被修饰的是对象引用,所引用的对象的成员变量必须是一些不变值,因为volatile修饰的是引用本身,对引用的对象并不起作用。
5. The cheap read-write lock trick
Java代码
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++;
}
}
这个模式是对那些非原子性操作加上同步锁,而原子性操作则保留了volatile变量的效率。
整篇文章在教我们如何正确使用volatile,实际上还是颇多陷阱,这也是引入java.util.concurrent.atomic包的原因。比如执行getAndDecrement这类操作,使用atomic包远比模式五要简单。