修饰符volatile学习笔记

最近学习java多线程,碰巧项目中用到AtomicReference类,在网上搜罗了一堆资料,记录一下阅读笔记和一些文章网址供以后复习。这些文章都和volatile有关,通过学习volatile可以知道jdk1.5引入java.util.concurrent.atomic包的原因。

 

第一篇文章是“javaeye问答”llade回答问题“加不加volatile看不出有什么效果”的答案:

package linyumin.test.thread;  

/** 

 * @author llade 

 */ 

public class VolatileObjectTest {  

 

    /** 

     * 相信绝大多数使用JAVA的人都没试出volatile变量的区别。献给那些一直想知道volatile是如何

     * 工作的而又试验不出区别的人。 

     * 成员变量boolValue使用volatile和不使用volatile会有明显区别的。 

     * 本程序需要多试几次,就能知道两者之间的区别的。 

     * @param args 

     */ 

    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 修饰的时候,程序会很快退出,因为volatile 保证各个线程工作内存

    //的变量值和主存一致。所以boolValue == !boolValue就成为了可能。 

    boolean boolValue; 

 

    public void waitToExit() {  

        //非原子操作,理论上应该很快会被打断。实际不是,因为此时的boolValue在线

        //程自己内部的工作内存的拷贝,因为它不会强制和主存区域同步,线程2修改

        //boolValue很少有机会传递到线程一的工作内存中。所以照成了假的原子现象 

        if(boolValue == !boolValue)System.exit(0); 

    }  

      

    public void swap() {

        //不断反复修改boolValue,以期打断线程1.  

        boolValue = !boolValue;  

    } 

} 

这份代码说明了两个问题:1、线程间共享的变量会拷贝到线程的工作内存,如果不能及时写回到主存,将造成线程间共享变量不同步;2、加上volatile修饰符使得线程间共享变量同步会引起a == !a之类的逻辑错误,这是因为我们没有对非原子性操作boolValue == !boolValue加锁。所以可以修改这份代码来修正这两个错误:

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)中的上限和下限分别赋值,这可能导致下限大于上限。

// 违反条件一,为value加上volatile修饰符也可能出现value值少于预期值

public class CheesyCounter {

    private int value;

 

    public int getValue() { return value; }

 

    public int increment() {

        return value++;

    }

}

 

// 违反条件二,为lowerupper加上volatile修饰符可能出现lower > upper的情况

public class NumberRange {

    private int lower, upper;

 

    public int getLower() { return lower; }

    public int getUpper() { return upper; }

 

    public void setLower(int value) {

        if (value > upper)

            throw new IllegalArgumentException(...);

        lower = value;

    }

 

    public void setUpper(int value) {

        if (value < lower)

            throw new IllegalArgumentException(...);

        upper = value;

    }

}

引入volatile修饰符的原因有两个:使用简单,效率高,如果能够在恰当的时机使用它对编写高效的代码很有益,有五种模式:

1.   Status flags

volatile boolean shutdownRequested;

 

...

 

public void shutdown() { shutdownRequested = true; }

 

public void doWork() {

    while (!shutdownRequested) {

        // do stuff

    }

}

在多线的情况下,某个线程调用shutdown函数,volatile修饰符保证变量shutdownRequested的更新值能够被及时写入主存供其他线程调用。

2.   Onetime 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);

        }

    }

}

这个模式的用法与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

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

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,变量的settergetter加上volatile修饰符可以保证线程安全,这些gettersetter不能包含对变量的逻辑操作,假如被修饰的是对象引用,所引用的对象的成员变量必须是一些不变值,因为volatile修饰的是引用本身,对引用的对象并不起作用。

5.   The cheap read-write lock trick

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包远比模式五要简单。

 

最后收藏一个网址http://www.cs.umd.edu/~pugh/java/memoryModel/,这个网址记录了很多java虚拟机内存模型的资料。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值