Java并发15:并发三特性-有序性定义、有序性问题与有序性保证技术

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

在 Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例中,对并发中的三个特性(原子性、可见性和有序性)进行了初步学习。
本章主要就Java中保障有序性的技术进行更加全面的学习。

1.整体回顾

有序性定义:即程序执行的顺序按照代码的先后顺序执行。
Java自带有序性:happens-before原则。

2.有序性问题

其他大牛们经常拿下面的代码作为有序性的示例:

//线程1:
context = loadContext();   //语句1
inited = true;             //语句2

//线程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

不过本人并没有通过实际编程运行,来证明此段程序的无序性。

为了更形象的理解有序性问题,本人使用了后面的示例,虽然后面的示例对有序性体现不够准确。

如果各位看官,有更好的能够实际体现有序性问题的示例,请一定告知,十分感谢!

场景说明:

  • 有两个线程A和线程B。
  • 线程A对变量x进行加法和减法操作。
  • 线程B对变量x进行乘法和出发操作。

代码:
这里的示例只是为了方便得到无序的结果而专门写到,所以有些奇特。

static String a1 = new String("A : x = x + 1");
static String a2 = new String("A : x = x - 1");
static String b1 = new String("B : x = x * 2");
static String b2 = new String("B : x = x / 2");

//不采取有序性措施,也没有发生有序性问题.....
LOGGER.info("不采取措施:单线程串行,视为有序;多线程交叉串行,视为无序。");
new Thread(() -> {
    System.out.println(a1);
    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(a2);
}).start();
new Thread(() -> {
    System.out.println(b1);
    System.out.println(b2);
}).start();

运行结果:

2018-03-18 00:16:20 INFO  ConcurrentOrderlyDemo:63 - 不采取措施:单线程串行,视为有序;多线程交叉串行,视为无序。
A : x = x + 1
B : x = x * 2
B : x = x / 2
A : x = x - 1

通过运行结果发现,多线程环境中,代码是交替的串行执行的,这样会导致产生意料之外的结果。

3.有序性技术保障

在Java中提供了多种有序性保障措施,这里主要涉及两种:

  • 通过synchronized关键字定义同步代码块或者同步方法保障有序性。
  • 通过Lock接口保障有序性。

3.1.synchronized关键字

定义一个对象用于同步块:

//定义一个对象用于同步块
static byte[] obj = new byte[0];

在多线程环境中进行synchronized关键字的有序性测试:

LOGGER.info("通过synchronized保证有序性:成功");
//通过synchronized保证有序性
new Thread(() -> {
    synchronized (obj) {
        System.out.println(a1);
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(a2);
    }
}).start();
new Thread(() -> {
    synchronized (obj) {
        System.out.println(b1);
        System.out.println(b2);
    }
}).start();

运行结果(多次):

2018-03-18 11:02:25 INFO  ConcurrentOrderlyDemo:79 - 通过synchronized保证有序性:成功
A : x = x + 1
A : x = x - 1
B : x = x * 2
B : x = x / 2

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

3.2.Lock接口

定义一个Lock锁:
//定义一个Lock锁 
static ReentrantLock reentrantLock = new ReentrantLock(true);

在多线程环境中进行Lock接口的有序性测试:

LOGGER.info("通过Lock保证有序性:成功");
//通过Lock保证有序性
new Thread(() -> {
    reentrantLock.lock();
    System.out.println(a1);
    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(a2);
    reentrantLock.unlock();
}).start();
new Thread(() -> {
    reentrantLock.lock();
    System.out.println(b1);
    System.out.println(b2);
    reentrantLock.unlock();
}).start();

运行结果(多次):

2018-03-18 11:03:34 INFO  ConcurrentOrderlyDemo:100 - 通过Lock保证有序性:成功
A : x = x + 1
A : x = x - 1
B : x = x * 2
B : x = x / 2

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

4.总结

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

  • synchronized关键字
  • Lock接口

并发三特性总结

特性 volatile关键字 synchronized关键字 Lock接口 Atomic变量
原子性 无法保障 可以保障 可以保障 可以保障
可见性 可以保障 可以保障 可以保障 可以保障
有序性 一定程度保障 可以保障 可以保障 无法保障
 
 
 

 

posted @ 2021-08-19 16:53  姚春辉  阅读(260)  评论(2编辑  收藏  举报