Java并发编程之可见性与有序性

什么是可见性:

可见性:一个线程对主内存的修改可以及时的被其他线程观察到。

 

导致共享变量在线程间不可见的原因:

1、线程交叉执行

2、重排序结合线程交叉执行

3、共享变量更新后的值没有在工作内存与主内存间及时更新

 

对于可见性,jvm提供了synchronizedvolatile关键字。

JMM关于sysnchronized的两条规定:

一、线程解锁前,必须把共享变量的最新值刷新到主内存

二、线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(加锁与解锁是同一把锁)

 

volatile则是通过内存屏障和禁止重排序优化实现:

对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存

对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。

Volatile变量在被线程访问时,都强迫从主内存中读取,而该变量发送变量的时候。又会强迫线程将最新的值刷新到主内存。

volatile都是在cpu指令级别进行操作。volatile是无法保证变量安全的:

@Slf4j
public class CountExample4 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static volatile int count = 0;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        count++;
        // 1、count
        // 2、+1
        // 3、count
    }
}

 

查看结果:

分析:

count++,其实分为三步,第一步去取当前内存的最新值,+1和重新写回主存的操作。假设两个线程分别执行到第一步,取得主存中最新的值。而后线程一做加一操作,虽然会写回主存,使工作内存二中的缓存行失效,但是无法改变上一步中取得的值了。因此volatile无法保证变量的线程安全性。

 

有序性:

Java内存模型中,允许编辑器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

在java里可以通过volatile来保证一定的有序性,另外也可以通过synchroized和lock来保证有序性。synchroized和lock是保证每个时刻是只有一个线程执行同步代码,相当于是让线程顺序执行代码从而保证有序性(单线程看有序,不会改变最终的结果,但是多线程情况下就不一定了)。Java具备一些先天的有序性(不需要任何手段就能保证有序性),即happens-before原则(先行发生原则)。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证有序性。虚拟机可以随意的对它们进行重排序。

happens-before八个原则:

 

程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。

一段程序的执行,在单个线程中看起来是有序的。程序次序规则看起来是按顺序执行的,因为虚拟机可能会对程序指令进行重排序。虽然进行了重排序但是最终执行的结果是与程序顺序执行的结果是一致的。它只会对不存在数据依赖行的指令进行重排序。该规则是用来保证程序在单线程执行结果的正确性。但是无法保证程序在多线程执行结果的正确性。

 

锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。即无论在单线程还是多线程中,同一个锁如果处于被锁定状态,那么必须先对锁进行释放操作,后面才能继续执行lock操作。

 

volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作。

 

传递规则:如果操作A先行发生于操作B,而操作B先行发生于操作C,则可以得出操作A先行发生于操作C

 

线程启动原则:Thread对象的start()方法先行发生于此线程的每一个动作

 

线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。

 

线程终结规则:线程中所以的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thhread.isAlive()的返回值手段检测到线程已经终止执行。

 

对象终结规则:一个对象的初始化完成先行于它的finalize(()方法的开始。

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_34871626/article/details/81513916
今日推荐