你真的理解Java变量的可见性和原子性吗?("深入解析:你真的掌握Java变量的可见性与原子性吗?")

原创
ithorizon 6个月前 (10-20) 阅读数 18 #后端开发

深入解析:你真的掌握Java变量的可见性与原子性吗?

一、引言

在Java多线程编程中,变量的可见性和原子性是两个非常重要的概念。领会这两个概念对于编写高效、正确的并发程序至关重要。本文将深入解析Java变量的可见性和原子性,帮助读者更好地掌握这两个概念。

二、可见性

可见性指的是当一个线程修改了共享变量的值后,其他线程能够立即得知这个修改。在Java中,为了实现变量的可见性,JMM(Java Memory Model)提供了happens-before原则。

2.1 happens-before原则

happens-before原则定义了一些规则,遵循这些规则可以保证并发环境下的可见性。以下是一些重点的happens-before规则:

  • 程序顺序规则:在一个线程内,按照代码顺序,前面执行的操作happens-before后面执行的操作。
  • 监视器锁规则:一个线程在获取锁之前,必须先看到另一个线程释放锁的操作。
  • volatile变量规则:对一个volatile变量的写操作happens-before后续对这个变量的读操作。
  • 线程启动规则:一个线程的start()方法happens-before该线程的任何操作。
  • 线程join规则:一个线程的join()方法happens-before该线程的终止。

2.2 示例代码

public class VisibilityExample {

private static boolean flag = false;

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

// 线程1

Thread thread1 = new Thread(() -> {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

flag = true;

});

// 线程2

Thread thread2 = new Thread(() -> {

while (!flag) {

// 循环等待

}

System.out.println("线程2看到了线程1对flag的修改");

});

thread1.start();

thread2.start();

}

}

在上面的代码中,如果flag没有声明为volatile,线程2也许永远无法看到线程1对flag的修改,使程序无法正常完成。这是由于没有happens-before规则来保证flag的可见性。

三、原子性

原子性指的是一个操作在多线程环境下,要么全部执行,要么全部不执行,不会出现部分执行的情况。在Java中,对于基本数据类型的变量,除了long和double之外,所有变量的读取和写入操作都是原子的。但是复合操作(如自增、自减等)并不是原子的。

3.1 示例代码

public class AtomicityExample {

private static int count = 0;

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

// 创建100个线程

for (int i = 0; i < 100; i++) {

Thread thread = new Thread(() -> {

for (int j = 0; j < 1000; j++) {

count++;

}

});

thread.start();

}

// 等待所有线程执行完毕

Thread.sleep(1000);

System.out.println("最终的count值为:" + count);

}

}

在上面的代码中,count++操作并不是原子的,它实际上包含了三个操作:读取count的值、增多1、将新值写入count。在多线程环境下,这些操作也许会被交错执行,使最终的count值小于预期。

3.2 原子操作类

Java提供了一系列原子操作类,如AtomicInteger、AtomicLong等,这些类通过使用volatile关键字和Unsafe类提供的原子操作方法,保证了操作的原子性。

public class AtomicityExample {

private static AtomicInteger atomicInteger = new AtomicInteger(0);

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

// 创建100个线程

for (int i = 0; i < 100; i++) {

Thread thread = new Thread(() -> {

for (int j = 0; j < 1000; j++) {

atomicInteger.incrementAndGet();

}

});

thread.start();

}

// 等待所有线程执行完毕

Thread.sleep(1000);

System.out.println("最终的count值为:" + atomicInteger.get());

}

}

在上面的代码中,我们使用了AtomicInteger类来保证自增操作的原子性。这样,无论多少线程同时执行,最终的计数都是正确的。

四、总结

领会Java变量的可见性和原子性对于编写并发程序至关重要。通过遵循happens-before原则和使用原子操作类,我们可以确保在多线程环境下共享变量的正确性和一致性。期望本文能够帮助读者更好地掌握这两个概念,从而编写出更高效、更稳定的并发程序。


本文由IT视界版权所有,禁止未经同意的情况下转发

文章标签: 后端开发


热门