volatile关键字解析

mac2024-04-03  56

volatile关键字提供两个保证,其一保证修饰的变量对各个线程的可见性;其二防止指令重排序。

如何实现可见性?

对volatile变量的写操作会写回主内存,并且对volatile变量的写操作会导致其它线程工作内存中该变量失效,使得其它线程再使用该变量时需要先从主内存读取,因此保证了可见性。

如何防止指令重排序?

通过插入内存屏障,来保证对volatile变量的读写操作,与其它指令的顺序不会被重排。

volatile关键字能够保证对其修饰的变量进行 读取 或 写入 操作时是原子操作,且保证对其修饰的变量进行写入操作是具有先行发生原则的(即对其进行写入操作能够使得其它对该变量的读取操作读取到的变量值都是最新的),但是volatile并不能保证其它操作也是原子操作,仅仅是保证读取 写入操作而已。注意这个地方,很容易忽视这个定义导致出现异常。下面的代码中开了100个线程,每个线程都对n进行连续10次加1操作,由于n是类变量,因此所有线程都是对同一个n进行操作,理想结果是n从0开始每次递增1,递增至1000。但是运行结果并非如此。

public class VolatileTest implements Runnable{ public static volatile int n = 0; @Override public void run() { for(int i = 0; i < 10; i++){ // volatile只能保证对n的读取和写入操作是原子操作 // 并且保证写入操作是先行发生的 // 但是volatile不能保证其它操作也是原子操作 // 比如这里的 + 1 操作就不是原子操作 // 虽然每个线程读取n的值以及加1操作之后的给n写入值是原子操作且写入保证线性发生 // 但是加1操作本身不是原子的,可能发生多个线程交替执行加1操作 n = n + 1; System.out.println("Thread name: " + Thread.currentThread().getName() + ", n的值是: " + n); try { Thread.sleep(3000); } catch (InterruptedException e) { System.out.println("线程: " + Thread.currentThread().getName() + "暂停3秒异常"); } } } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[100]; for (int i = 0; i < 100; i++){ threads[i] = new Thread(new VolatileTest()); } System.out.println("100个线程开始对n进行加1操作"); for (int i = 0; i < 100; i++) { threads[i].start(); } for (int i = 0; i < 100; i++) { threads[i].join(); } System.out.println("100个线程对n进行加1操作结束"); System.out.println("n : " + n); } }

原因就在volatile上,n = n + 1;这个操作包括读取n的值、进行+ 1操作、写入n中,由于volatile的存在,读取n的值、写入n中是原子操作,但是+1操作并非原子操作。因此可能会有多个线程交替执行+1操作。

最新回复(0)