并发三大特性理解

115 阅读3分钟

并发三大特性

原子性

一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。在 Java 中,对基本数据类型的变量的读取和赋值操作是原子性操作(64位处理器)。不采取任何的原子性保 障措施的自增操作并不是原子性的,比如i++操作。

原子性案例分析

下面例子模拟多线程累加操作

` public class AtomicTest {

private static int counter = 0;

public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        Thread thread = new Thread(() -> {
            for (int j = 0; j < 10000; j++) {
                counter++;
            }

        });
        thread.start();
    }
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(counter);
}

} `

执行结果不确定, 与预期结果不符合,存在线程安全问题

image.png

如何保证原子性

  • 通过 synchronized 关键字保证原子性
  • 通过 Lock锁保证原子性
  • 通过 CAS保证原子性

可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到 修改的值。

可见性案例分析

下面是模拟两个线程对共享变量操作的例子,用来分析线程间的可见性问题

` public class VisibilityTest {

private boolean flag = true;

public void refresh() {
    // 希望结束数据加载工作
    flag = false;
    System.out.println(Thread.currentThread().getName() + "修改flag:" + flag);

}

public void load() {
    System.out.println(Thread.currentThread().getName() + "开始执行.....");
    while (flag) {
        //TODO 业务逻辑:加载数据

    }
    System.out.println(Thread.currentThread().getName() + "数据加载完成,跳出循环");

}


public static void main(String[] args) throws InterruptedException {
    VisibilityTest test = new VisibilityTest();

    // 线程threadA模拟数据加载场景
    Thread threadA = new Thread(() -> test.load(), "threadA");
    threadA.start();

    // 让threadA先执行一会儿后再启动线程B
    Thread.sleep(1000);

    // 线程threadB通过修改flag控制threadA的执行时间,数据加载可以结束了
    Thread threadB = new Thread(() -> test.refresh(), "threadB");
    threadB.start();

}

} `

运行结果:threadA没有跳出循环,也就是说threadB对共享变量flag的更新操作对threadA不可见, 存在可见性问题。

如何保证可见性

  • 通过 volatile 关键字保证可见性
  • 通过 内存屏障保证可见性
  • 通过 synchronized 关键字保证可见性
  • 通过 Lock锁保证可见性

有序性

即程序执行的顺序按照代码的先后顺序执行。为了提升性能,编译器和处理器常常会对指令做重排 序,所以存在有序性问题。

有序性案例分析

思考:下面的Java程序中x和y的最终结果是什么?

` public class ReOrderTest {

private static int x = 0, y = 0;
private static int a = 0, b = 0;

public static void main(String[] args) throws InterruptedException {
    int i = 0;
    while (true) {
        i++;
        x = 0;
        y = 0;
        a = 0;
        b = 0;
        /**
         * x,y的值是多少:
         */
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //用于调整两个线程的执行顺序
                shortWait(20000);
                a = 1;

                x = b;
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                b = 1;
                y = a;
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("第" + i + "次(" + x + "," + y + ")");
        if (x == 0 && y == 0) {
            break;
        }
    }
}
public static void shortWait(long interval) {
    long start = System.nanoTime();
    long end;
    do {
        end = System.nanoTime();
    } while (start + interval >= end);
}

}

` 执行结果:x,y出现了0,0的结果,程序终止。出现这种结果有可能是重排序导致的

image.png

如何保证有序性

  • 通过 volatile 关键字保证有序性
  • 通过 内存屏障保证有序性
  • 通过 synchronized关键字保证有序性
  • 通过Lock锁保证有序性

JMM线程模型