volatile实践,你了解多少?

说volatile之前先要了解主内存,工作内存,

对于多线程来说,注意三个特性:1.原子性,2.有序性,3.可见性,这里不讲述

volatile规则:

    1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

    2.行指令重排序。

    3.保证有序性,可见性,不保证原子性

使用volatile关键字会强制将修改的值立即写入主存,如当线程2进行修改时,会导致线程1的工作内存中缓存变量无效,由于线程1的工作内存中缓存变量无效所以会再次去主内存中去取

如此,我们可以写一段代码验证一下

public static void main(String[] args) {
        new Thread(){
            Test test = new Test();
            public void run(){
                test.test1();
            }
        }.start();

        new Thread(){
            public void run(){
                test.test2();
            }
        }.start();
    }

     int a = 0;

    public  void test1(){
        String name = Thread.currentThread().getName();
        System.out.println(name + " start");
        a = a+1;
        System.out.println(name+" a1--"+ a);
    }
    public  void test2(){
        String name = Thread.currentThread().getName();
        System.out.println(name+" a1 -- " + a);
    }

可以看到,我起了两个线程分别调用test1对a做自增,test2打印,结果是什么呢?

Thread-0 start
Thread-0 a1--1
Thread-1 a1 -- 0
Thread-0 start
Thread-0 a1--1
Thread-1 a1 -- 1

两种情况都可能,也就是说如果test1计算后将值写入主内存后test2才执行,这时是第二种从主内存中得到的是更改后的

如果test1计算后还没来得及由工作内存写入主内存test2就开始执行了,从主内存中获取的是更改前的值后,test1才将值更新到主内存中,这就是第一种,就有问题了

而用volatile修饰变量a就可以解决这个问题,结果就是第二种

一开始偶然间犯了一个小错误,从而变得更有意思些

public static void main(String[] args) {
        new Thread(){
            public void run(){
                new Test().test1();
            }
        }.start();

        new Thread(){
            public void run(){
                new Test().test2();
            }
        }.start();
    }

    volatile int a = 0;

    public  void test1(){
        String name = Thread.currentThread().getName();
        System.out.println(name + " start");
        a = a+1;
        System.out.println(name+" a1--"+ a);
    }
    public  void test2(){
        String name = Thread.currentThread().getName();
        System.out.println(name+" a1 -- " + a);
    }

看下这样的运行结果是什么呢?

Thread-0 start
Thread-0 a1--1
Thread-1 a1 -- 0

 虽然有volatile修饰,但是Thread-1得到的还是0,这是为什么呢?后来找了找原因才看到,虽然起了两个线程,但是也是起了两个test实例,每个线程分别new了一个实例,那么获取的也是不同的内存,不同的副本,这个怎么改一下呢?

要么改成最上面那样new一个实例进行调用

要么就加上static    即:static volatile int a = 0 ,这样共享一个实例就可以了


还有一个问题,volatile不保证原子性怎么办?,可以看下这个

volatile int a = 0;
    public static void main(String[] args) {
        final Test test = new Test();
        new Thread(){
            public void run(){
                test.test1();
            }
        }.start();
        new Thread(){
            public void run(){
                test.test2();
            }
        }.start();
    }

    public  void test1(){
        String name = Thread.currentThread().getName();
        System.out.println(name + " start-" + a);
        int b = a;
        System.out.println(name + "  b--"+ b);
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        a = b +1;
        System.out.println(name+" a1--"+ a);
    }
    public void test2(){
        String name = Thread.currentThread().getName();
        a = a+1;
        System.out.println(name+" a1 -- " + a);
    }

这个运行是什么?

Thread-0 start-0
Thread-0  b--0
Thread-1 a1 -- 1
Thread-0 a1--1

为什么是这个结果?自增了两次,那么Thread-0和Thread-1结果应该是1和2啊,因为他们同时从主内存中获取a为0,test1()已经声明变量b也指向a的值0,然后等待,这时test2()执行+1操作,写入主内存为1,test1()睡醒b+1也为1写入a的主内存,那么就绕过了volatile的特性

解决办法:synchronized,对同一变量+1操作采取同步

                 Lock lock = new ReentrantLock();两者具体怎么用就不说了

基础知识还是得狠狠补一下的啊,要想质的飞跃,就得脚踏实地

发布了45 篇原创文章 · 获赞 44 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/Goligory/article/details/89648177