Java并发读书笔记:线程安全与互斥同步
目录
本篇参考许多著名的书籍,形成读书笔记,便于加深记忆。 前文传送门:Java并发读书笔记:JMM与重排序 导致线程不安全的原因当一个变量被多个线程读取,且至少被一个线程写入时,如果读写操作不遵循 什么是线程安全Brian Goetz在《Java并发编程实战》中是这样定义的:
周志明在《深入理解Java虚拟机》中提到:多个线程之间存在共享数据时,这些数据可以按照线程安全程度进行分类: 不可变不可变的对象一定是线程安全的,只要一个不可变的对象被正确地构建出来,那么它在多个线程中的状态就是一致的。例如用final关键字修饰对象:
JavaAPI中符合不可变要求的类型:String类,枚举类,数值包装类型(如Double)和大数据类型(BigDecimal)。 绝对线程安全即完全满足上述对于线程安全定义的。 满足该定义其实需要付出很多代价,Java中标注线程安全的类,实际上绝大多数都不是线程安全的(如Vector),因为它仍需要在调用端做好同步措施。Java中绝对线程安全的类: 相对线程安全即我们通常所说的线程安全,Java中大部分的线程安全类都属于该范畴,如 线程兼容对象本身并不是线程安全的,可以通过在调用段正确同步保证对象在并发环境下安全使用。如我们之前学的分别与Vector和HashTable对应的 对象通过synchronized关键字修饰,达到同步效果,本身是安全的,但相对来说,效率会低很多。 线程对立无论调用端是否采取同步措施,都无法正确地在多线程环境下执行。Java典型的线程对立:Thread类中的suspend()和resume()方法:如果两个线程同时操控一个线程对象,一个尝试挂起,一个尝试恢复,将会存在死锁风险,已经被弃用。 常见的对立: 互斥同步实现线程安全互斥同步也被称做阻塞同步(因为互斥同步会因为线程阻塞和唤醒产生性能问题),它是实现线程安全的其中一种方法,还有一种是非阻塞同步,之后再做学习。 互斥同步:保证并发下,共享数据在同一时刻只被一个线程使用。 synchronized内置锁其中使用
它包含了两部分:1、锁对象的引用 2、锁保护的代码块。 每个Java对象都可以作为用于同步的锁对象,我们称该类的锁为监视器锁(monitor locks),也被称作内置锁。 可以这样理解:线程在进入synchronized之前需要获得这个锁对象,在线程正常结束或者抛出异常都会释放这个锁。 而这个锁对象很好地完成了互斥,假设A持有锁,这时如果B也想访问这个锁,B就会陷入阻塞。A释放了锁之后,B才可能停止阻塞。 锁即对象
明确:synchronized方法和代码块本质上没啥不同,方法只是对跨越整个方法体的代码块的简短描述,而这个锁是方法所在对象本身(static修饰的方法,对象是当前类对象)。这个部分可以参考:Java并发之synchronized深度解析 是否要释放锁释放锁的情况:
不释放锁的情况:
实现原理通过对.class文件反编译可以发现,同步方法通过 虽然两者实现细节不同,但其实本质上都是JVM基于进入和退出Monitor对象来实现同步,JVM的要求如下:
啥是重进入?重进入意味着:任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞, 这就体现了锁计数器的作用:获得一次锁加一,释放一次锁减一,无论获得还是释放多少次,只要计数为零,就意味着锁被成功释放。 ReentrantLock(重入锁)
RenntrantLock官方推荐的基本写法:
API层面的互斥锁ReentrantLock表现为API层面的互斥锁,通过 等待可中断当持有线程长期不释放锁的时候,正在等待的线程可以选择放弃等待或处理其他事情。 公平锁ReentrantLock锁是公平锁,即保证等待的多个线程按照申请锁的时间顺序依次获得锁,而synchronized是不公平锁。 锁绑定一个ReentrantLock对象可以同时绑定多个Condition对象。 JDK1.6之前,ReentrantLock在性能方面是要领先于synchronized锁的,但是JDK1.6版本实现了各种锁优化技术,后续性能改进会更加偏向于原生的synchronized。 参考数据:《Java并发编程实战》、《Java并发编程的艺术》、《深入理解Java虚拟机》 (编辑:北几岛) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |