并发编程之可见性

一个小程序

public class TestThread {
	private static boolean flag = true;

	public static void main(String[] args) throws InterruptedException {

		Thread t1 = new Thread(() -> {
			while (flag) {

			}
            System.out.println("t1 end");
		});
		t1.start();
		Thread.sleep(1000);
		flag = false;
        System.out.println("flag set to false");
	}
}

输出结果:

flag set to false

  在上面的小程序中,创建一个线程t1,这个线程判断flag为true的时候进行死循环,当flag为false的时候结束循环。启动线程,1秒之后将flag设为false。但是期望的结果并没有出现,线程t1并没有退出循环。这说明主线程修改flag的值之后,线程t1并没有感知到flag的变化。

究其原因

在这里插入图片描述
  如图,线程t1执行时,会将flag从主存中拷贝一个副本到其线程本地(CPU缓存)中,每一次while循环读取的都是本地的flag。同理,主线程执行时,也会从主存中拷贝一个flag副本到其线程本地,将flag设置为false时,修改的是本地flag的值,并没有修改主存中flag的值。在上面的小程序中,线程t1看不到flag值的修改,所以线程t1永远不会结束。

使用volatile修饰变量实现可见性

public class TestThread {
	private static volatile boolean flag = true;

	public static void main(String[] args) throws InterruptedException {

		Thread t1 = new Thread(() -> {
			while (flag) {

			}
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 end");
		});
		t1.start();
		Thread.sleep(1000);
		flag = false;
        System.out.println("flag set to false");
	}
}

输出:

flag set to false
t1 end

  在上面的代码中,只是将flag用volatile修饰,就得到了我们预期的结果,主线程将flag设为false之后,线程t1感知到flag的修改,退出循环,结束线程。原因在于,使用volatile修饰的变量,每一次读取都需要从主存中读取,对该变量的修改,每一次修改完成之后都要将新值刷新到主存中,由此保证了变量在线程之间的可见性。

volatile修饰引用类型

  当使用volatile修饰引用类型的时候,只能保证引用本身的可见性,不能保证内部字段的可见性。比如:

public class TestThread {
    private static class A {
        boolean flag = true;
        void m() {
            while (flag) {}
            System.out.println("hahah end");
        }
    }
    private static volatile A a = new A();

	public static void main(String[] args) throws InterruptedException {

		new Thread(a::m).start();
		Thread.sleep(1000);
		a.flag = false;
        System.out.println("a.flag set to false");
	}
}

其他触发缓存同步刷新的情况

  除了volatile之外,还存在一些语句会触发缓存同步刷新。如:System.out.println()和Thread.sleep()。println()方法加了synchronized,锁机制保证了每次执行都会把共享内存中的数据同步到工作内存中。而Thread.sleep()方法是因为,CPU在空闲的时候会主动同步刷新缓存中的数据。

public class TestThread {
    private static boolean flag = true;

	public static void main(String[] args) throws InterruptedException {
		new Thread(() -> {
		    while (flag) {
                System.out.println();
            }
            System.out.println("end");
        }).start();
		Thread.sleep(1000);
		flag = false;
        System.out.println("flag set to false");
	}
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值