显示锁解析

显示锁Lock

Lock是一个接口,提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有的加锁和解锁操作方法都是显示的,因而称为显示锁。

Lock接口和synchronized的比较:
1,synchronized代码更简洁
2,Lock,效率比隐士锁更高
3,Lock可以在获取锁可以被中断,超时获取锁,尝试获取锁

Lock接口有三个实现类:
1,ReentrantLock,
2,ReentrantReadWriteLock类中的两个静态内部类ReadLock和WriteLock。

	ReentrantReadWriteLock类实现了ReadWriteLock接口。

ReentrantLock(可重入锁,构造函数可以指定是否是公平锁,默认是非公平锁)

可重入锁:一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁。
不可重入锁:即当前线程获取这把锁后,要想再拿到这把锁,必须释放当前持有的锁,这时我们称这个锁是不可重入的。
ReentrantLock内部实现了一个非公平锁和公平锁:
非公平锁(NonfairSync):继承Sync(内部实现了AQS的锁),其中在tryAcquire方法中,判断如果是当前线程,就会将state状态值一直累加,实现锁的可重入,同时,在释放的时候依次递减状态值。
公平锁(fairSync):继承Sync(内部实现了AQS的锁),在获取锁的时候,首先根据hasQueuedPredecessors方法判断当前节点有没有前驱节点,如果有的话,就将当前线程设置为挂起状态,其他跟非公平锁实现一致。

锁的公平和非公平

如果在时间上,先对锁进行获取的请求,一定先被满足,这个锁就是公平的,不满足,就是非公平的,意思就是,线程等待队列中的优先级高的线程先获取到锁,就是公平锁,没有获取到锁,就是非公平锁,非公平的效率一般来讲更高,因为非公平锁减少了线程挂起的几率,后来的线程有一定几率逃离被挂起的开销。

ReentrantLock和ReentrantReadWriteLock

ReentrantLock和synchronized:都是排他锁,同一时刻只允许一个线程访问。
ReentrantReadWriteLock读写锁:读写锁实际是一种特殊的自旋锁,同一时刻允许多个读线程同时访问,但是写线程访问的时候,所有的读和写都被阻塞,最适宜与读多写少的情况。读写锁在内部有两个实现了Lock接口的静态内部类,读锁和写锁。
同步状态state:
同步状态由一个整型变量表示,因为这个变量需要表示多个线程的读和写的状态,因此读写锁在实现上将该变量的高16位表示读,低16位表示写,其中每位的读线程重入的次数是由HoldCounter对象包装之后放入到ThreadLocal中。
注意:
1,在读多写少的情况下,性能比一般的排他锁和Syn关键字要高。
2,当有读锁时,写锁就不能获得,而当有写锁时,当前线程还可以获取读锁,其他线程不能获取读锁,同一线程可以保证数据可见性。 如果持有读锁的时候去获取写锁,这就是所谓锁的升级,这是不允许的,因为读锁可能有多个线程获取了,如果允许获得写锁,那就 真正可能产生可见性性问题。注意:(可见性是针对不同线程而言)
3,锁降级:
锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。

	原理:
		锁降级存在争议,在很多本书里面出现过,锁降级中,读锁的获取的目的是“为了保证数据的可见性”。而得到这个结论的依据是 “如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么当前线程无法感知线程 T 的数据更新”。这里貌似有个漏洞:如果另一个线程获取了写锁(并修改了数据),那么这个锁就被独占了,没有任何其他线程可以读到数据,更不用谈 “感知数据更新”。
		所以,主要是性能上的优化,如果先释放写锁,再获取读锁,势必引起锁的争抢和线程上下文切换,影响性能,所以通过锁降级,可以避免锁的竞争。

4,锁升级:
在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的。
5,读锁本质上是个共享锁。但读锁对锁的获取做了很多优化,比如:

	1,使用firstReader和cachedHoldCounter对第一个读锁线程和最后一个读锁线程做优化,优化点主要在释放的时候对计数器的获取,其他获取读锁的线程就放在HoldCounter中。
	2,同时,如果在获取读锁的过程中写锁被持有,JUC并没有让所有线程痴痴的等待,而是判断入如果获取读锁的线程是正巧是持有写锁的线程,那么当前线程就可以降级获取写锁,否则就会死锁了(为什么死锁,当持有写锁的线程想获取读锁,但却无法降级,进入了等待队列,肯定会死锁)。
	3,还有一点就是性能上的优化,如果先释放写锁	,再获取读锁,势必引起锁的争抢和线程上下文切换,影响性能。

6,在读锁和写锁的获取过程中支持中断。
7,提供确定锁是否被持有等辅助方法。

Condition

显示锁中的条件接口Condition,包含如等待和唤醒之类的方法,Condition对象是由Lock对象(调用Lock对象的newCondition()方法)创建出来的,换句话说,Condition是依赖Lock对象的。
在显示锁中,唤醒是用signal,而最好不用signalAll,因为signal可以精准唤醒指定的线程,而在隐士锁中用notifyAll,而不用notify,因为notify不能精准唤醒,可能导致死锁。signalAll只能唤醒指定Condition上的等待的线程,其他线程也不能被唤醒,和notifyAll不同。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值