java线程中的锁
java中的锁的概念
自旋锁: 是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
乐观锁: 假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改。
悲观锁: 假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。
独享锁(写):给资源加上写锁,线程可以修改资源,其他线程不能再加锁; (单写)
共享锁(读): 给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁; (多读)
可重入锁、不可重入锁: 线程拿到一把锁之后,可以自由进入同一把锁所同步的其他代码。
公平锁、非公平锁: 争抢锁的顺序,如果是按先来后到,则为公平。
同步关键字
1、用于实例方法、静态方法时,隐式指定锁对象
2、用于代码块时,显示指定锁对象
3、锁的作用域:对象锁、类锁、分布式锁
4、引申:如果是多个进程,怎么办?
特性: 可重入、独享、悲观锁
特殊优化 : 锁消除 (开启锁消除的参数:-XX:+DoEscapeAnalysis -XX:+EliminateLocks) 、锁粗化 JDK做了锁粗化的优化, 但我们自己可从代码层面优化
同步关键字的原理
默认情况下JVM锁会经历:未锁定->偏向锁 -> 轻量级锁 -> 重量级锁 这四个状态
轻量级锁
在未锁定的状态下,可以通过CAS来抢锁,抢到的是轻量级锁。
image.png
重量级锁
轻量级锁中的自旋有一定的次数限制,超过了次数限制,轻量级锁升级为重量级锁。
image.png
偏向锁
在JDK6 以后,默认已经开启了偏向锁这个优化,通过JVM 参数 -XX:-UseBiasedLocking 来禁用偏向锁若偏向锁开启,只有一个线程抢锁,可获取到偏向锁
锁升级的过程
image.png
偏向标记第一次有用,出现过争用后就没用了。 -XX:-UseBiasedLocking 禁用使用偏置锁定,偏向锁,本质就是无锁,如果没有发生过任何多线程争抢锁的情况,JVM认为就是单线程,无需做同步(jvm为了少干活:同步在JVM底层是有很多操作来实现的,如果是没有争用,就不需要去做同步操作)
重量级锁 - 监视器
修改mark word如果失败,会自旋CAS一定次数,该次数可以通过参数配置:超过次数,仍未抢到锁,则锁升级为重量级锁,进入阻塞
monitor也叫做管程,计算机操作系统原理中有提及类似概念。一个对象会有一个对应的monitor。
image.png
wait/notify机制
wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。
注意:
1、只能在synchronized关键字中使用,且调用wait、notify的对象与锁对象相同,否则会抛出IllegalMonitorStateException异常。
2、wait() 方法调用后,会破坏原子性。
锁的本质
场景:土豪小区车位紧张,只有0357车位还未出售 此时,此时大家都想买0357车位买了0357车位,才能在这儿停车 (加锁)跟小区签订合同后,你就具备了停车0357的资格就说你获得了锁会有一把车位锁,来象征你的0357车位停车资格。
locks包 类层次结构
image.png
void lock(); 获取锁(不死不休)
boolean tryLock(); 获取锁(浅尝辄止)
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 获取锁(过时不候)
void lockInterruptibly() throws InterruptedException; 获取锁(任人摆布)
void unlock(); 释放锁
Condition newCondition();
结论:
1、lock()最常用;
2、lockInterruptibly()方法一般更昂贵,有的impl可能没有实现lockInterruptibly(),
只有真的需要效应中断时,才使用,使用之前看看impl对该方法的描述。
Reentrantlock
image.png
Condition
用于替代wait/notify。
Object中的wait(),notify(),notifyAll()方法是和synchronized配合使用的,可以唤醒一个或者全部(单个等待集);
Condition是需要与Lock配合使用的,提供多个等待集合,更精确的控制(底层是park/unpark机制);
image.png
同步关键字VS lock
Synchronized
- 优点:
1、使用简单,语义清晰,哪里需要点哪里。
2、由JVM提供,提供了多种优化方案(锁粗化、锁消除、偏向锁、轻量级锁) 3、锁的释放由虚拟机来完成,不用人工干预,也降低了死锁的可能性 - 缺点:
无法实现一些锁的高级功能如:公平锁、中断锁、超时锁、读写锁、共享锁等
lock
优点:
1、所有synchronized的缺点
2、可以实现更多的功能,让synchronized缺点更多
- 缺点:
需手动释放锁unlock,新手使用不当可能造成死锁
ReadAndWrite Lock
维护一对关联锁,一个只用于读操作,一个只用于写操作;
读锁可以由多个读线程同时持有,写锁是排他的。同一时间,两把锁不能被不同线程持有。
适合读取操作多于写入操作的场景,改进互斥锁的性能,比如:集合的并发线程安全性改造、缓存组件。
锁降级指的是写锁降级成为读锁。持有写锁的同时,再获取读锁,随后释放写锁的过程。
写锁是线程独占,读锁是共享,所以写->读是降级。(读->写,是不能实现的)