Java并发14:并发三特性-可见性定义、可见性问题与可见性保证技术

在Java并发编程中,如果要保证代码的安全性,则必须保证代码的原子性、可见性和有序性。

在 Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例中,对并发中的三个特性(原子性、可见性和有序性)进行了初步学习。

本章主要就Java中保障可见性的技术进行更加全面的学习。

1.整体回顾

可见性定义:当一个线程修改了共享变量的值,其他线程能够看到修改的值。

2.可见性问题

场景说明:

  • 存在两个线程A、线程B和一个共享变量stop。
  • 如果stop变量的值是false,则线程A会一直运行。如果stop变量的值是true,则线程A会停止运行。
  • 线程B能够将共享变量stop的值修改为ture。

定义普通的共享变量:

//普通情况下,多线程不能保证可见性
private static boolean stop;

下面的代码展示了在多线程环境中,对共享变量的共享:

//普通情况下,多线程不能保证可见性
new Thread(() -> {
    System.out.println("Ordinary A is running...");
    while (!stop) ;
    System.out.println("Ordinary A is terminated.");
}).start();
Thread.sleep(10);
new Thread(() -> {
    System.out.println("Ordinary B is running...");
    stop = true;
    System.out.println("Ordinary B is terminated.");
}).start();

某次运行结果:

Ordinary A is running...
Ordinary B is running...
Ordinary B is terminated.

通过观察结果,发现程序确实存在可见性问题。

3.可见性技术保障

在Java中提供了多种可见性保障措施,这里主要涉及四种:

  • 通过volatile关键字标记内存屏障保证可见性。
  • 通过synchronized关键字定义同步代码块或者同步方法保障可见性。
  • 通过Lock接口保障可见性。
  • 通过Atomic类型保障可见性。

3.1.volatile关键字

使用volatile关键字修饰共享变量stop:

//使用volatile能够保证可见性
private volatile static boolean vStop;

在多线程环境中,对volatile修饰的共享变量stop进行共享测试:

//通过volatile关键字保证可见性
new Thread(() -> {
    System.out.println("Volatile A is running...");
    while (!vStop) ;
    System.out.println("Volatile A is terminated.");
}).start();
new Thread(() -> {
    System.out.println("Volatile B is running...");
    vStop = true;
    System.out.println("Volatile B is terminated.");
}).start();

运行结果(多次):

Volatile A is running...
Volatile B is running...
Volatile B is terminated.
Volatile A is terminated.

通过多次运行,发现运行结果一致,所以可以确定volatile关键字能够保证代码的可见性。

3.2.synchronized关键字

定义一个共享对象用于synchronized关键字进行同步加锁:

  //通过synchronized同步代码块保证可见性
    private static byte[] obj = new byte[0];

在多线程环境中进行对obj进行加锁、等待和唤醒:

//通过synchronized同步代码块保证可见性
new Thread(() -> {
    System.out.println("Synchronized A is running...");
    synchronized (obj) {
        try {
            obj.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while (!stop) ;
    }
    System.out.println("Synchronized A is terminated.");
}).start();
Thread.sleep(10);
new Thread(() -> {
    System.out.println("Synchronized B is running...");
    synchronized (obj) {
        stop = true;
        obj.notify();
    }
    System.out.println("Synchronized B is terminated.");
}).start();

运行结果(多次):

Synchronized A is running...
Synchronized B is running...
Synchronized B is terminated.
Synchronized A is terminated.

通过多次运行,发现运行结果一致,所以可以确定synchronized关键字能够保证代码的可见性。

3.3.Lock接口

定义Lock接口和Condition接口:

//通过Lock接口保证可见性
private static ReentrantLock lock = new ReentrantLock(true);
private static Condition condition = lock.newCondition();

在多线程环境中进行Lock加锁、condition等待和唤醒:

//通过Lock同步代码块保证可见性
new Thread(() -> {
    System.out.println("Lock A is running...");
    lock.lock();
    try {
        condition.await();
        while (!stop) ;
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
    System.out.println("Lock A is terminated.");
}).start();
new Thread(() -> {
    System.out.println("Lock B is running...");
    lock.lock();
    try {
        stop = true;
        condition.signal();
    } finally {
        lock.unlock();
    }
    System.out.println("Lock B is terminated.");
}).start();

运行结果(多次):

Lock A is running...
Lock B is running...
Lock B is terminated.
Lock A is terminated.

通过多次运行,发现运行结果一致,所以可以确定Lock接口能够保证代码的可见性。

3.4.Atomic类型

使用AtomicBoolean定义共享变量stop:

//通过Atomic保证可见性
private static AtomicBoolean aStop = new AtomicBoolean(false);

在多线程环境中,对使用AtomicBoolean定义的共享变量stop进行共享测试:

//通过Atomic保证可见性
new Thread(() -> {
    System.out.println("Atomic A is running...");
    while (!aStop.get()) ;
    System.out.println("Atomic A is terminated.");
}).start();
new Thread(() -> {
    System.out.println("Atomic B is running...");
    aStop.set(true);
    System.out.println("Atomic B is terminated.");
}).start();

运行结果(多次):

Atomic A is running...
Atomic B is running...
Atomic B is terminated.
Atomic A is terminated.

通过多次运行,发现运行结果一致,所以可以确定Atomic类型能够保证代码的可见性。

4.总结

经验证,以下四种措施,可以保证Java代码在运行时的可见性:

  • volatile关键字
  • synchronized关键字
  • Lock接口
  • Atomic类型

并发三特性总结

特性 volatile关键字 synchronized关键字 Lock接口 Atomic变量
原子性 无法保障 可以保障 可以保障 可以保障
可见性 可以保障 可以保障 可以保障 可以保障
有序性 一定程度保障 可以保障 可以保障 无法保障
posted @ 2021-08-19 15:30  姚春辉  阅读(306)  评论(0编辑  收藏  举报