• Java并发拾遗(四)——锁


    一、锁的语义

    锁机制是Java中最重要的同步机制,其能够使临界区的代码互斥执行,且执行的结果对下一个拿到锁的线程立即可见。个人感觉,大家普遍对锁的互斥性有普遍的理解,很容易忽略了锁提供的可见性的保证。试想,如果锁仅仅提供了互斥,在临界区代码执行完之后,不把相应的执行结果刷回主内存,那么下一个线程拿到锁之后,很可能看不到上一个线程的执行结果,这必然会出现问题的。所以,锁的语义包含了volatile的语义:

    1. 互斥性:临界区代码互斥执行

    2. 原子性:临界区代码的执行对外具有原子性

    3. 可见性:临界区的代码的结果在锁释放时刷回主内存,对下一个拿到锁的线程立即可见

    4. 重排序:以临界区为屏障,禁止临界区两侧的指令刺穿屏障进行重排序

    二、锁语义的实现

    在Java很多的并发工具类中,都依赖了AQS(AbstractQueuedSynchronizer)来实现同步。而在AQS中,偷偷的藏了一个volatile变量state,在加锁的时候,读这个变量,在释放锁的时候,写这个变量。so,利用这个volatile变量,很鸡贼的实现了上面的可见性与重排序的保证。

    至于锁如何实现互斥性与原子性,这就得看Java AQS的源代码了。

    三、补充(2017.5.7)

    之前以为已经理解了锁的语义,然而实际上并木有。。。在effective Java中,有个double-check的栗子,代码如下:

        private volatile FieldType field;
        FieldType getField() {
            FieldType result = field;
            if (result == null) {
                synchronized (this) {
                    result = field;
                    if (result == null) {
                        field = result = new FieldType();
                    }
                }
            }
            return result;
        }  

    代码本身容易懂,然而当按照之前对锁语义的理解,即锁能够保证可见性(即在同步块内部对变量的写之后,其他所有线程都是对这个变量立即可见的)。这样子的话,就无法解释对field声明为volatile的意义了,因为synchronize已经完全足够了。

    在深入理解之后,发现自己之前的理解有偏差,syachronized不是万能的。synchronize仅仅能保证自己写完的变量,对下一个获取到这个锁的变量是可见的。要是这时候存在另一个线程,并没有获取锁,而只是普通的读,那么是不一定能看到刚刚线程再同步块内部的写操作的结果的。因此,这段代码才需要将field声明为volatile,以来弥补外边一层check的普通读,否则double-check很可能就失去意义了。

    换句话说,要是这段代码将两次check全部放在代码块里,就不会有问题了。然而当外层的check是普通的读时,就一定得加上volatile来解决可见性问题了,不是把共享变量塞到同步块中完成写操作就万事大吉了,还需要考虑到读这个变量的可见性问题。

    换另一个角度,加锁时,相当于对共享变量的volatile读,释放锁时相当于volatile写。volatile 写 happens-before 后续对这个变量的volatile读,然而,volatile 写并不happens-before 后续对这个变量的普通读。

  • 相关阅读:
    【Ubuntu】set参数介绍
    【英语学习】第14周翻译练习之日本教育
    【英语学习】第13周翻译练习之自然光照对工作的影响
    【英语学习】第12周翻译练习之出国交流心得
    【英语学习】第11周翻译练习之网上购物
    【英语学习】第9周翻译练习之大学专业
    【英语学习】第10周翻译练习之博物馆是否应当免费开放
    数据结构与算法之选择排序(C++)
    数据结构与算法之冒泡排序(C++)
    机械臂开发之DH模型
  • 原文地址:https://www.cnblogs.com/dosmile/p/6736111.html
Copyright © 2020-2023  润新知