原创

第一篇 : volatile 关键字与内存可见性


Java 提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。 可以将 volatile 看做一个轻量级的锁,但是又与 锁有些不同:

  • 对于多线程,不是一种互斥关系
  • 不能保证变量状态的“原子性操作”

一、问题 ?

我们先看代码

package com.gf.demo;


public class TestVolatile {

    public static void main(String args[]){
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();

        while (true) {
            if (td.isFlag()) {
                System.out.println("----------------");
                break;
            }
        }
    }

}

class ThreadDemo implements Runnable {

    private boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep( 200 );
            flag = true;
            System.out.println( flag );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

你会发现一个问题 : main线程一直运行,新建立的 Thread 和 main线程 共享 td 对象 ,为什么 main线程 不打印虚线呢 ?

二、原因

如图所示,线程1 和 main线程在访问 flag 是都是主存中复制一份到自己的本地,然后操作 ,线程1 修改 flag = true,同步到主存中,虽然主存中的flag 变成了true,然是由于main线程中的while(true) 调用的是计算机比较底层的方法,所以,执行效率非常高 ,跟没有机会与主存中的数据同步。也就是说多线程操作共享数据时,彼此不可见。

三、解决方法

1. synchronized 同步锁

package com.gf.demo;


public class TestVolatile {

    public static void main(String args[]){
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();

        while (true) {
            synchronized(td){
                if (td.isFlag()) {
                    System.out.println("----------------");
                    break;
                }
            }
        }
    }

}

class ThreadDemo implements Runnable {

    private boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep( 200 );
            flag = true;
            System.out.println( flag );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

加了synchronized 解决了问题,但是加锁 ,就必然有锁竞争,效率低下。

2. volatile

package com.gf.demo;

/**
 * 一、volatile 关键字:当多个线程进行操作共享数据时,可以保证线程间共享数据的可见性。
 *                     相比与synchronized 是一种较为轻量级的同步策略。
 *                     
 * 注意:
 *  1. volatile 不具备 "互斥性"
 *  2. volatile 不能保证变量的原子性
 */
public class TestVolatile {

    public static void main(String args[]){
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();

        while (true) {
            if (td.isFlag()) {
                System.out.println("----------------");
                break;
            }
        }
    }

}

class ThreadDemo implements Runnable {

    private volatile boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep( 200 );
            flag = true;
            System.out.println( flag );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

通过上面的例子我们可以得出下面的结论:

  • volatile 当多个线程进行操作共享数据时,可以保证线程间共享数据的可见性
  • volatile 不具备 "互斥性"
  • volatile 不能保证变量的原子性(下一章节讲解)
juc
  • 作者:程序员果果
  • 发表时间:2018-10-29 09:06
  • 版权声明:自由转载-非商用-非衍生-保持署名 (创意共享4.0许可证)
  • 公众号转载:请在文末添加作者公众号二维码
  • 评论