Java锁——锁机制整理

锁重入

具体的一个场景就是在一个同步域内调用本类的其他由synchronized修饰的方法或代码块时,是永远可以得到锁的

synchronized就拥有锁重入的特性.

能够避免死锁:线程获得某对象的锁后,未释放状态下再次获取该对象的锁时,如果不支持重入,就会造成死锁.

Lock

在这里插入图片描述

ReentrantLock

static abstract class Sync extends AbstractQueuedSynchronizer
final static class NonfairSync extends Sync
final static class FairSync extends Sync

排他锁,重入锁 支持公平锁和非公平锁,提供了构造函数允许设置是否为公平锁,默认是非公平锁的

使用

public static void main(String[] args) {
    Lock lock = new ReentrantLock();
    // Condition condition = lock.newCondition();// 创建Condition,可以存在多个
    new Thread(() -> doSome(lock), "thread1").start();
    new Thread(() -> doSome(lock), "thread2").start();
}
private static void doSome(Lock lock) {
    try {lock.lock();}
    ......;
    finally{lock.unlock();}
}

创建ReentrantLock对象时可以指定锁是否为公平锁,构造方法如下:

public ReentrantLock(boolean fair) {
   sync = fair ? new FairSync() : new NonfairSync();
}

原理

内部类Sync

ReadWriteLock

ReentrantReadWriteLock

ReentrantLock锁是完全排他锁,同一时间只有一个线程能执行lock.lock()和lock.unlock()之间的代码.

而ReentrantReadWriteLock则通过创建2个实现了Lock接口的内部类ReadLock,WriteLock来维护一对锁,实现性能的提升.

  • 读锁:共享,重入
  • 写锁:排他,重入

由于写锁是排他的,只要涉及到写操作都是互斥的,一个线程获取到写锁后不释放写锁也能继续获取到读锁,即降级为读锁.

获取锁:

new ReentrantReadWriteLock().readLock();
new ReentrantReadWriteLock().writeLock();

锁状态的记录

ReentrantLock中是使用AQS中的state属性记录锁的状态,ReentrantReadWriteLock有2把锁,其状态怎样记录?

在同一个整形变量上,表示2把锁的状态,就需要将这个变量按位分割使用,高16位记录读锁,低16位记录写锁.

锁优化

锁竞争及上下文切换会导致程序性能的下降,因此对锁的优化也是有必要的.

减少锁持有的时间:和降低锁粒度很相似,不要将耗时的操作放入同步域
减小锁粒度:Java7中ConcurrentHashMap的实现就利用了这种优化方式
读写锁替换独占锁:ReentrantReadWriteLock区别ReentrantLock的地方就体现出这种方式,只有涉及到写操作才会阻塞
锁分离:读写锁的基础上更进一步,LinkedBlockingQueue(基于链表的阻塞队列,读写分离)就使用了这种方式,存在takeLock和putLock两种锁
锁粗化:多个锁合并为同一个锁,与减小锁粒度相反,增大了同步域以避免频繁切换上下文,所以有特殊的适用场景
锁消除:编译器发现不可能被共享的对象,可以消除这些对象的锁操作,如某些类的方法时加了锁,但却用在不会有线程安全问题的情形下
JVM的优化:偏向锁, 轻量级锁, 重量级锁等

JVM的优化

synchronized的锁是互斥锁, 对性能有很大影响, 因此从Java6开始引入了偏向锁和轻量级锁, 以及锁的存储结构和锁升级机制.

synchronized锁是存在Java对象头中, 数组类型用3个字宽(Word)存储对象头, 其他类型用2个字宽(32位VM下1字宽为4字节).
在这里插入图片描述
对象头的Mark Word(32位):
在这里插入图片描述
其中, 锁状态共有4种, 级别从低到高依次是: 无锁状态, 偏向锁状态, 轻量级锁状态和重量级锁状态, 锁可以升级且不能逆向
在这里插入图片描述
在这里插入图片描述
自旋锁: 避免挂起线程的一种尝试.如果线程一直无法获得锁, 并且能获取的时间点也未知, 就会让当前线程做几个空循环
偏向锁: 偏向锁就是一旦线程第一次获得了监视对象, 之后让监视对象“偏向”这个线程, 之后的多次调用则可以避免CAS操作
轻量级锁: 偏向锁运行在一个线程进入同步块的情况下, 当第二个线程加入锁竞争的时候, 偏向锁就会升级为轻量级锁
重量级锁: JVM中又叫对象监视器(Monitor), 它至少包含一个竞争锁的队列和一个信号阻塞队列(wait队列), 前者负责做互斥, 后一个用于做线程同步.
在这里插入图片描述

死锁预防

以确定的顺序获得锁:在设计时即考虑到不同线程间锁获取的顺序. 超时放弃:使用synchronized获取锁时,只要没有获取到锁就会一直等待,但Lock接口实现了tryLock(),可以指定超时时间,

Q&A

synchronized和ReentrantLock

synchronized和java.util.concurrent.locks.Lock的异同

还包括了中断锁等待和定时锁等待

在并发量小的时候, 用synchronized是比较好的选择, 并发量大的时候用Lock. Lock有比synchronized更精确的线程语义和更好的性能. synchronized是自动释放锁, Lock是主动释放锁, 并且必须在finally语句中释放 Lock可中断, 可以设定所等待的时间, 可尝试获取锁(tryLock(long timeout, TimeUnit unit), 等待可中断), 可绑定多个Condition 有些操作不会发生冲突现象, 需要用Lock解决, 比如同时读文件 synchronized是非公平锁, ReentrantLock默认情况下也是非公平锁, 但可以通过带布尔值的构造函数要求使用公平锁. 都是可重入的.
参考:
Java锁——synchronized和锁(ReentrantLock) 区别
Java锁——AbstractQueuedSynchronizer加锁和解锁分析
Java并发——AQS、AQS到底什么是AQS?这玩意干啥的?

lock的原理是什么?

通过AQS(AbstractQueuedSynchronizer)来维护一个int FIFO等待队列管理获取锁的所有线程. 在“公平锁”的机制下, 线程依次排队获取锁; 而“非公平锁”在锁是可获取状态时, 不管自己是不是在队列的开头都会获取锁.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冒菜-码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值