线程同步 ——volatile

上一篇咱们简单的使用了synchoronized实现线程同步,接下来简单的使用volatile实现线程同步。java

public class ThreadCount {
    
    //若是去掉volatile就会出现错误
    private static volatile int total;

    public static void main(String[] args) {

        Counter counter1 = new Counter(10);
        Counter counter2 = new Counter(20);
        Counter counter3 = new Counter(5);
        Counter counter4 = new Counter(10);
        Counter counter5 = new Counter(10);
        Counter counter6 = new Counter(20);
       
        counter1.start();
        counter2.start();
        counter3.start();
        counter4.start();
        counter5.start();
        counter6.start();   
    }

     static class Counter extends Thread {
        private int i;

        Counter(int i) {
            this.i = i;

        }

        @Override
        public void run() {
            synchronized (this) {
                total += i;
                System.out.println("结果为: " + total);
            }
        }
    }

}
//程序运行结果
结果为: 10
结果为: 30
结果为: 35
结果为: 45
结果为: 55
结果为: 75
//去掉volatile程序运行结果
结果为: 30
结果为: 45
结果为: 35
结果为: 30
结果为: 75
结果为: 55

一个关键字volatile可让程序即可以上程序正确的执行,volatile有什么神通呢,接下来让我娓娓道来:缓存

在jmm(java内存模型)中,线程共享主内存,其次每一个线程有本身私有的缓存,正常状况下线程更新主内存数据的时候得先更新本身的缓存的数据,而后更新主内的数据,而使用volatile后,线程直接更新住内存数据,这样就保证了当前线程更新共享数据后对别的线程当即得知这个修改。多线程

Java内存模型的抽象示意图以下:ide

                              

对于i++下i被volatile修饰,多线程下能获得正确的结果吗?请看下面的例子:this

public class VolatileTest {
    public static volatile int race = 0;

    public static void increase(){
        race++;
    }

    private static final int THREAD_COUNT = 200;

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREAD_COUNT];
        for(int i = 0; i < THREAD_COUNT; i++){
            threads[i] = new Thread(new Runnable() {
                public void run() {
                    for(int i = 0; i < 2000; i++){
                        increase();
                    }
                }
            });

            threads[i].start();
        }

        while (Thread.activeCount() > 1) {
            Thread.yield();
            System.out.println(race);

        }
    }
}
//执行结果
393715
393715
393715
393715
393715
393715

你会发现执行结果小于咱们预期400000,其实不管执行多少次都会小于400000,这个是为何呢?反编译这段代码发现increase()方法产生四条指令,1:getstiatic   2:iconst_1  3: iadd   4:putstatic,当getstatic指令把race的值渠道操做栈顶时,volatile只保证了race在此时准确,可是执行iconst_一、iadd指令时可能其余的线程把race的值增大,而这个时候栈顶已经变为实效的数据,因此putstatic可能会被较小的race放会到主内存,其实还有编译出来只有一条字节码指令,也不意味着这条指令在机器执行时是一个原子操做。spa

因此说volatile只能保证可见行,不能保证原子性,咱们仍然须要加锁来保证原子性 (使用synchoronized或java.util.concurrent中的原子类)线程

synchoronized如何保证原子性:code

Java内存模型提供lock和unlock操做来保证原子性,尽管虚拟机没有把lock和unlock直接开放给用户使用,可是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操做,这两个字节吗指令反映到java代码中就是同步块synchoronized关键字。内存

相关文章
相关标签/搜索