什么是volatile关键字

volatile关键字的作用:保证了变量的可见性(visibility)。被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象。
例如以下程序:

public class Test {

    public static void main(String[] args) {
        DemoThread thread = new DemoThread();
        new Thread(thread).start();
        while (true) {
            if (thread.isFlag()) {
                System.out.println("---------------");
                break;
            }
        }
    }
}

class DemoThread implements Runnable {

    private boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        setFlag(true);
        System.out.println("flag is " + isFlag());
    }

    public boolean isFlag() {
        return flag;
    }

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

这段程序,会不会打印出横线呢?显然是不会的,为什么呢?这个时候就涉及到了内存可见性。

内存可见性

实际上,当我们的程序运行时,JVM会为每一个执行任务的线程分配一个独立的缓存以提高其运行效率。所以当两个及以上的线程操作共享数据时,彼此不可见。原理如下:
图片说明

解决

使用synchronized关键字。

public class Test {

    public static void main(String[] args) {
        DemoThread thread = new DemoThread();
        new Thread(thread).start();
        while (true) {
            synchronized (thread) {
                if (thread.isFlag()) {
                    System.out.println("---------------");
                    break;
                }
            }
        }
    }
}

class DemoThread implements Runnable {

    private boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        setFlag(true);
        System.out.println("flag is " + isFlag());
    }

    public boolean isFlag() {
        return flag;
    }

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

但是synchronized关键字效率低,当第一个线程获取到锁之后,之后的线程会对锁进行检测,如果锁被占用,则会被阻塞,然后等待后续的cpu调度执行。有没有更好的方法呢?

volatile

volatile关键字的作用是当多个线程进行操作共享数据时,可以保证内存中的数据可见。我们也可以将其理解为多个线程操作被volatile关键字修饰的数据时相当于直接操作的主存中的数据。这样对各个变量来说,这些被修饰的数据都是实时可见的。但是volatile关键字的效率也低,被volatile关键字修饰的变量不能进行JVM底层的优化(重排序),但是其效率是比synchronized关键字高的。volatile相较于synchronized是一种较为轻量级的同步策略。

public class TestVolatile {

    public static void main(String[] args) {
        DemoThread thread = new DemoThread();
        new Thread(thread).start();
        while (true) {
            if (thread.isFlag()) {
                System.out.println("---------------");
                break;
            }
        }
    }
}

class DemoThread implements Runnable {

    private volatile boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        setFlag(true);
        System.out.println("flag is " + isFlag());
    }

    public boolean isFlag() {
        return flag;
    }

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

注意

1.volatile关键字不具备互斥性。synchronized具备互斥性。
2.volatile关键字仅仅能保证变量写操作的原子性,不保证复合操作,比如说读写操作的原子性