乐趣区

关于java:java多线程volatile的使用

volatile 关键字

次要 作用:

​ 1. 保证数据之间的可见性。

​ 2. 禁止指令重排序。

1. 可见性

2. 做个小的测试

public class VolatileTest implements Runnable {
    // 当为 false 时线程完结
    private static /*volatile*/ boolean flag = true;
    private static int value = 100;
    @Override
    public void run() {
        // TODO Auto-generated method stub

        while(flag) {
            value++;
            //System.out.println(value);// 能够勾销正文试一试
        }
        System.out.println(Thread.currentThread().getName()+"完结");
    }
    public static void main(String[] args) throws InterruptedException {new Thread(new VolatileTest() ).start();
        Thread.sleep(1000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                flag = false;
                System.out.println(Thread.currentThread().getName()+"完结");
            }
        }).start();
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName()+"完结");
        System.out.println("flag="+flag);
    }

}

后果:

Thread- 1 完结
main 完结
flag=false

咱们能够发现, 第二个线程将 flag 改成 false, 然而第一个线程并没有进行运行。

1. 多线程内存模型

3. 为什么?

从第一幅图咱们能够看出, 各一个线程都有一个工作内存, 线程运行时, 他会从主内存读取数据到工作内存, 而后在应用工作内存, 执行完在 save 到工作内存. 然而其余线程感知不到主内存的变动, 不晓得主内存的 flag 变成了 false, 所以没有更新本人的工作空间中的 flag(因为没有操作让他去主内存读取数据), 导致 flag 为 true, 所以循环无奈终止.

4.volatile 的作用:强制让其读取主内存, 而不是工作空间, 多个线程应用的为同一个空间, 就保障了可见性.

5.volatile 保障了可见性然而却没有保障原子性. 须要其余操作来实现。

如果将 flag 的 volatile 增加上。

后果:

Thread- 1 完结
Thread- 0 完结
main 完结
flag=false

2. 禁止指令重排序(有序性)

volatile 禁止 jvm 和处理器对 volatile 润饰的指令进行重排序,然而修饰符前和后的指令没有明确的规定。

何为重排序:

​ 在单线程下:jvm 为了进步执行的效率,会对咱们的代码进行优化,对咱们的指令进行地位的更换,然而更换有个前提,就是在单线程下逻辑不变,比方:

int a = 1;
int b = 2;
int c = a + b;
// 更换为
int b = 2;
int a = 1;
int c = a + b;

这种更换不会扭转后果(单线程下)。

同时对于 cpu 来说,为了满足效率问题,也会对咱们的指令进行重排序,进步 cpu 流水线 的效率。

重排序规定:

int a = 1;
int b = 2;
volatile int c = 3;
int d = 4;
int f = 6;

volatile 能够禁止重排序,然而只针对润饰的命令,对于下面的程序,a,b 没有润饰,所以,a,b 能够重排序,同时 d,f 也能够,然而 ab 和 df 是不会进行重排序的,因为 volatile 生成 内存屏障

(1)volatile 写操作后面插入一个 StoreStore 屏障。确保在进行 volatile 写之前后面的所有一般的写操作都曾经刷新到了内存。

(2)volatile 写操作前面插入一个 StoreLoad 屏障。防止 volatile 写操作与前面可能存在的 volatile 读写操作产生重排序。

(3)volatile 读操作前面插入一个 LoadLoad 屏障。防止 volatile 读操作和前面一般的读操作进行重排序。

(4)volatile 读操作前面插入一个 LoadStore 屏障。防止 volatile 读操作和前面一般的写操作进行重排序。

简略来说,volatile 润饰的后面的指令不会和前面的指令进行重排序,同时运行 volatile 润饰时,后面的代码全副执行结束。

举个重排序的例子:

public class Disorder {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        int count = 0;
        long start = System.currentTimeMillis();
        while(true){
            count++;
            x = 0; y = 0;
            a = 0; b = 0;
            Thread one = new Thread(() -> {
                a = 1;
                x = b;
            });
            Thread other = new Thread(() -> {
                b = 1;
                y = a;
            });
            one.start();other.start();
            one.join();other.join();
            if (x == 0 && y ==0){long end = System.currentTimeMillis();
                System.out.println("程序运行次数:"+count);
                System.out.println("程序耗时:"+(end-start));
                break;
            }
        }
    }
}

退出,咱们认为程序是一行一行执行的,即程序不会产生扭转。

状况 1(one) 状况 1(other) 状况 2(one) 状况 2(other) 状况 3(one) 状况 3(other)
a = 1 a = 1 a = 1
x = b b = 1 b = 1
b = 1 x = b y = a
y = a y = a x = b
后果 a=1
x=0
b=1
y=1
a=1
x=1
b=1
y=1
a=1
x=1
b=1
y=1
状况 4(one) 状况 4(other) 状况 5(one) 状况 5(other) 状况 6(one) 状况 6(other)
b = 1 b = 1 b = 1
a = 1 a = 1 y = a
x = b y = a a = 1
y = a x = b x = b
后果 a=1
x=1
b=1
y=1
a=1
x=1
b=1
y=1
a=1
x=1
b=1
y=0

依照上述排序,能够看出永远是不会呈现 x =0 和 y = 0 同时存在的状况呈现,那么这个程序就没有后果。

然而运行程序:

程序运行次数:5130
程序耗时:2453

程序胜利退出,示意呈现了 xy 同时为零的状况。这示意某个线程的指令没有依照程序执行,程序被打乱了。

退出移动版