正确理解volatile保证线程间的可见性的功能

76 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第27天,点击查看活动详情

正确理解volatile保证线程间的可见性的功能

今天我们一起来学习一下,volatile的其中一个功能,也是一个比较容易弄混的功能。 我们都知道volatile最主要的功能,有以下几个:

1.保证线程间可见性

2.禁止指令重排序

相信各位对于volatile的第二个功能(禁止指令重排序)已经很熟悉了,其实就是高级代码里划分的多个指令,在真正运行的时候,有可能进行重新排序,也就是和原来排序的顺序不一致;这样的情况下,可能会出现某些难以解释的问题,而volatile的出现,就可以很好地解决了这个问题。

那对于volatile的第一个功能,大家伙是不是也是同样熟悉呢?有没有理解全面??

下面咱们一起来验证一下!

volatile保证线程间基本数据类型的可见性

我们一般情况下,是用volatile来修饰基本数据类型的变量,这样来保证该变量的数据在各线程之间可见。 下面我们一起来看个例子: 新建一个线程 t1 ,t1调用一个testMethod方法一直在空转;这时候,使用主线程将 mark 的值改为false,看线程t1是否可以成功执行完毕;如果是线程间不可见(没有加volatile),则线程t1无法完毕。

public class T10_Visibleness {
        // 这里使用和不使用volatile的结果是完全不同的
        // 使用了volatile线程 t1 才能执行完毕
	 /*volatile*/ boolean mark = true;

	void testMethod() {
		System.out.println("start...");
		while (mark) {
			// System.out.println("recusion...");
		}
		System.out.println("end...");
	}

	public static void main(String[] args) {
		T10_Visibleness t10 = new T10_Visibleness();
		new Thread(t10::testMethod, "t1").start();

		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (Exception e) {
			e.printStackTrace();
		}

		t10.mark = false;
	}
}

volatile修饰引用对象时

下面这个例子就是讲述volatile修饰引用对象时的一个经典例子,我们一起来分析一下: 下面自定义了一个引用对象MyData,由一个setData线程通过for循环不断地创建新的对象,而这时候由getData线程去读取,这时候就会发现引用对象还是MyData,但是MyData里面的成员变量实际上已经改变了,可是这个点是别的线程无法感知到的。

public class T11_VolatileReference {
	private static class MyData {
		int a, b;

		public MyData(int a, int b) {
			this.b = b;
			this.a = a;
		}
	}

	volatile static MyData myData;

	public static void main(String[] args) {
		// 在这里不停地创建 data 实例
		Thread setData = new Thread(() -> {
			for (int i = 0; i < 10000; i++) {
				myData = new MyData(i, i);
			}
		});

		// 引用对象本身可见,
                // 但是其内部字段的可见性并不保证,
                // 从而导致了 a 和 b 的值可能出现不同
		Thread getData = new Thread(() -> {
			while (myData == null) {
			}
			int x = myData.a;
			int y =myData.b;
			if (x != y) {
				System.out.printf("a = %s, b=%s%n", x, y);
			}
		});

		setData.start();
		getData.start();

		try {
			getData.join();
			setData.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		System.out.println("end");
	}
}