本篇源自于本人以前的《JAVA安全与并发》一文。因其文太长,所以剔出。
锁
synchronized
synchronized是依赖于JVM来执行锁的。属于公平锁。悲观锁。
使用范围:代码块,方法,静态方法,类。
特性:不可中断,可读性好。
场景:不激烈的竞争场景,线程少。
实现:JVM实现。
ReentrantLock
ReentrantLock依赖于JDK来实现的。循环调用CAS来实现操作。
默认为非公平锁(可配)。乐观锁(只有在用到值时才进行CAS判断比较)。
特性:可中断,多样化同步。
场景:激烈的竞争场景,线程多。
实现:CAS+volatile的int。
ReentrantReadWriteLock
在没有任何读写锁时,才可以取得写入锁。其写入锁时悲观锁。读时乐观锁。
特性:两把锁。
场景:一般用于两个地方对相同多变量的修改和读取。
实现:CAS+volatile的int,基于高低16位来区分读或写。
*atomic和volatile不是锁,但在某些情况下,有着比锁更好或者更方便的使用
atomic
特性:比lock性能还好。但每次只能更新一个值。不属于锁,但和锁一样有原子性的功能。
场景:不激烈的竞争场景。
实现:基于CPU的CAS。
volatile
因为JVM对于代码会进行指令重排序,所以volatile出现了。volatile通过实现内存屏障
和禁止重排序
来实现的。不属于锁,但在部分场景中有同步的效果,且比锁性能好很多。
对于volatile的变量,在每次读之前会从主内存中load,在每次更新后会store到主内存中。
volatile不具有原子性。
volatile的使用需要满足:1.对变量的操作不依赖于当前值;2.该变量没有包含在具有其他变量不变的表达式中。
适用场景:1.作为状态标记量,2.双重检查。
实现:读写屏障。读屏障:loadload-读操作-loadstore;写屏障:storestore-写操作-storeload。
多线程共享变量不可见的原因:
- 线程交叉执行;
- 重排序结合线程交叉执行;
- 共享变量更新后,没有在工作内存和主内存中进行及时更新。
可重入性 | 锁的实现依赖 | 性能 | 便利性 | 灵活性 | 锁类型 | 唤醒机制 | 是否可以中断 | |
---|---|---|---|---|---|---|---|---|
ReentrantLock | 是 | 依赖于JDK | - | 低 | 高 | 公平锁和非公平锁 | 支持部分唤醒 | 是 |
synchronized | 是 | 依赖于JVM | - | 高 | 低 | 公平锁 | 唤醒一个或者全部 | 否 |
公平锁:每个线程抢占锁的顺序为先后调用lock方法的顺序依次获取锁,类似于排队吃饭。
非公平锁:每个线程抢占锁的顺序不定,谁运气好,谁就获取到锁,和调用lock方法的先后顺序无关,类似于堵车时,加塞的那些XXXX。
死锁
死锁的原因
- 系统资源不足;
- 进程运行推进的顺序不合适;
- 资源分配不当。
死锁的必然条件
出现死锁后,肯定会出现下面的现象:
- 互斥条件:一个资源每次只能被一个线程使用。
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源不释放。
- 不剥夺条件:线程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
网友评论