文章目录
锁
1 死锁
死锁是指两个或两个以上的线程在执行任务过程中,因争夺资源而造成的相互等待的现象
产生死锁的条件:
- 互斥条件:指线程对已经获取的资源进行排他性使用,即该资源同时只能由一个线程占用
- 请求并持有条件:指线程已经持有了至少一个资源,但又请求其他资源,如果请求不到则阻塞,但不释放已有的资源
- 不可剥夺条件:指线程获取到资源后,在使用完之前不能被其他线程占用
- 环路等待条件:指发生死锁时,必然存在一个线程——资源的环形链
避免死锁:破坏死锁产生的条件
- 破坏请求并持有条件:一次性获取所有所需的资源
- 破坏不可剥夺条件:占用资源的线程再获取其他资源获取不到时,释放已有的资源
- 破坏环路等待条件:按某一顺序申请资源,释放资源则按相反的顺序
2 锁的类型
2.1 乐观锁和悲观锁
悲观锁:指对数据被外界的修改持悲观态度,认为数据很容易被修改。因此操作前会先进行加锁,操作结束再释放锁。悲观锁的实现往往依靠数据库提供的锁机制
乐观锁:与悲观锁相反。因此在访问数据时不会加锁,只有对数据提交更新的时候,才会检测数据是否冲突。乐观锁通常通过version字段(CAS)或者使用业务状态来实现
2.2 公平锁和非公平锁
公平锁:线程获取锁的顺序是按照线程请求锁的顺序
非公平锁:通过抢占的方式来争夺锁
2.3 独占锁和共享锁
独占锁:一种悲观锁,保证任何时候仅有一个线程可以获得锁
共享锁:一种乐观锁,允许多个线程同时进行读操作
2.4 读写锁和排他锁
排他锁:同一时刻只允许一个线程访问
读写锁:内部维护一个读锁一个写锁,通过分离读锁和写锁,在读多写少情况下提高性能
2.5 可重入锁
当线程再次获取一个自己已经获取的锁时,如果不会被阻塞,则该锁是可重入锁
原理:在锁内部维护一个标识,标识哪个线程占用,一个计数器,计数该线程获取锁的次数
2.6 自旋锁
当线程在获取锁时,如果发现锁已经被占用,不会马上阻塞自己,而是不放弃CPU使用权的情况下,多次尝试获取
3 锁的等级
3.1 偏向锁
偏向锁在资源无竞争情况下消除了同步语句,连CAS操作都不做了,提高程序的运行性能
一个线程在进入同步块时,会检查锁的MarkWord里是不是自己的线程ID
如果是,则表明该线程已经获取偏向锁,以后该线程进入和退出同步块不需要花费CAS操作加锁和解锁
如果不是,则代表有另一个线程竞争偏向锁,尝试用CAS来修改MarkWord中的线程ID。如果成功,表示另一个线程不存在了(释放锁了),如果失败,则升级为轻量级锁
3.2 轻量级锁
线程尝试用CAS修改MarkWord中的线程ID,如果失败,则采用自旋来获取锁
如果自旋到一定程度,依然没有获取锁,则升级为重量级锁
3.3 重量级锁
重量级锁依赖于操作系统中的互斥量来实现
3.4 总结
偏向锁
- 优点:加锁和解锁不需要额外的消耗
- 缺点:如果线程间存在锁竞争,则会带来额外的锁撤销的消耗
- 适用:只有一个线程访问同步块
轻量级锁
- 优点:竞争的线程不会阻塞,提高了程序的响应速度
- 缺点:自旋会消耗CPU
- 适用:追求相应时间,同步块执行速度非常快
重量级锁
- 优点:不使用自旋,不会消耗CPU
- 缺点:线程阻塞,响应时间缓慢
- 适用:追求吞吐量,同步块执行时间较长