本文共 3079 字,大约阅读时间需要 10 分钟。
《Java并发编程实践:深入解析volatile关键字》
Java中的volatile关键字用于多线程编程,用于修饰一变量,使其在简单实现synchronized的效果。虽然初始解释较为简单,但volatile与synchronized的区别及使用细节对多线程开发至关重要。本文将详细解析volatile的实现原理及其在多线程环境中的应用。
在单线程环境中,CPU高速缓存与内存的数据传递过程是无缝的。然而,在多线程环境中,每个线程都有其自己的CPU和高速缓存。这种情况下,多个线程对同一变量进行操作时可能导致数据不一致。
以一条简单的语句x = x + 1
为例,数据流转过程如下:
在多线程环境下,若线程1在执行完第二步而线程2立即读取内存中的x,得到的可能仍为旧值。最终结果无法满足预期,可能仅增加一次,使得x的值小于预期。
在并发编程中,系统需要满足以下三大性质:
原子性要求所有操作要么全部执行完,要么全部打住。某些操作(如基本数据类型的加法)在Java中是原子操作,这意味着不会因线程调度而被打断。但复杂操作可能需要额外保护,如synchronized或Lock。
代码执行的顺序决定了结果的有效性。为了优化性能,系统可能会对代码进行重排序。然而,重排序可能导致结果不依赖依赖关系的改变,破坏有序性。例如,以下代码可能因为重排序而导致错误:
a = a + 1; // 依赖于a的初始值b = a + a; // 取决于a的值b = b + a; // 再次利用a的值
在多线程环境中,重排序问题更为严重。如下代码:
innited = false; // 线程1操作 doSomething(); // 线程2马上执行
因为inited
和doSomething()
无关,线程2可能直接执行,而忽略线程1的准备工作。
可见性要求所有线程在某一时刻知晓shared variable的变化。
Java内存模型规定主存是唯一管理中存储的介质,每个线程拥有一自己的工作内存。变量只能在工作内存中进行计算后,再写回主存。线程不可直接访问其他线程的工作内存,主存是所有线程的唯一共享资源。
下图展示了Java内存的结构:
主存 <-线程1-> 工作内存 <- cpu <- 寄存器 <-cache <- CPU | | | | | | | | | | | | | | | | | | | --------------------------读写 occurs here -------------------------
Java内存模型支持对基本数据类型变量的原子操作。这是指一个线程读取并修改变量时,其他线程无法读取或修改同一变量的中间值。例如:
x = x + 1;x++; // 这两步不是原子操作
上述代码并非原子操作,因为在完成x = x + 1
后,x
可能已经被另一个线程读取到新的值。
volatile变量的修改会立即同步至主存,其他线程的读取操作只能从主存获取数据。因此,volatile保证了可见性。但是,这种同步给性能带来了影响。
volatile变量的修改会立即写入主存,使得其它线程能够及时获取最新值。这避免了多线程环境下的缓存一致性问题。
例如,对int i = 0;
进行自增操作,可能执行以下步骤:
如果在第2步后另一个线程读取内存,此时i可能已被修改为1,但读取到的值可能还是原来的。这样,同一语句可能只执行一次,导致结果错误。
为了确保原子性和可见性,可以采用以下方式:
synchronized
关键字。Lock
机制。AtomicInteger
等原子类型。下面的示例显示了volatile和线程安全的区别:
public class VolatileTest { public volatile int inc = 0; public void increase() { inc++; } public static void main(String[] args) { final VolatileTest test = new VolatileTest(); Lock lock = new ReentrantLock(); for(int i = 0; i < 100; i++){ new Thread(() -> { for(int j = 0; j < 10000; j++) { try { lock.lock(); test.increase(); System.out.println(test.inc); } finally { lock.unlock(); } } }, "线程" + i).start(); } }}
与使用synchronized
或Lock
结合使用的版本相比,volatile
只能确保可见性,而不能保证原子性。
这篇文章实际上深入探讨了volatile的实现原理、与多线程编程的关系,并结合实际案例分析了其设计决策和局限性。
转载地址:http://ueupz.baihongyu.com/