多线程之重入锁锁ReentrantLock的深度理解《九》

1. 重入锁ReentrantLock

顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对
资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选择
这里提到一个锁获取的公平性问题,如果在绝对时间上,先对锁进行获取的请求一定先
被满足,那么这个锁是公平的,反之,是不公平的。公平的获取锁,也就是等待时间最长的线
程最优先获取锁,也可以说锁获取是顺序的。
事实上,公平的锁机制往往没有非公平的效率高,但是,并不是任何场景都是以TPS作为
唯一的指标,公平锁能够减少“饥饿”发生的概率,等待越久的请求越是能够得到优先满足
重入锁在同一时刻只允许一个线程进行访问
公平和非公平锁的队列都基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。
公平锁则在于每次都是依次从队首取值。
非公平锁:在等待锁的过程中, 如果有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的。
2. 听故事理解重入锁
2.1 公平锁模型
我们把锁的机制类比打水,打水时,以家庭为单位,哪个家庭任何人先到井边,就可以先打水,
而且如果一个家庭占到了打水权,其家人这时候过来打水不用排队。而那些没有抢占到打水权的人,
一个一个挨着在井边排成一队这就是公平锁的模型
就是一个线程在获取了锁之后,再次去获取了同一个锁,这时候仅仅是把状态值进行累加。
如果线程A释放了一次锁,仅仅是把状态值减了,只有线程A把此锁全部释放了,状态值减到0了,
其他线程才有机会获取锁。
当A把锁完全释放后,state恢复为0,然后会通知队列唤醒B线程节点,使B可以再次竞争锁。
当然,如果B线程后面还有C线程,C线程继续休眠,除非B执行完了,通知了C线程。
注意,当一个线程节点被唤醒然后取得了锁,对应节点会从队列中删除。 
2.2 非公平锁模型
新来打水的人,他们看到有人排队打水的时候,他们不会那么乖巧的就排到最后面去排队,
反之,他们会看看现在有没有人正在打水,如果有人在打水,没辄了,只好排到队列最后面,
但如果这时候前面打水的人刚刚打完水,正在交接中,排在队头的人还没有完成交接工作,
这时候,新来的人可以尝试抢打水权,如果抢到了,呵呵,其他人也只能睁一只眼闭一只眼,
因为大家都默认这个规则了。这就是所谓的非公平锁模型。
当线程A执行完之后,要唤醒线程B是需要时间的,而且线程B醒来后还要再次竞争锁,
所以如果在切换过程当中,来了一个线程C,那么线程C是有可能获取到锁的
如果C获取到了锁,B就只能继续乖乖休眠了

3.为什么要用可重入锁?

 对于 synchronized 来说,如果一个线程在等待锁,那么结果只有两种情况,
 1. 获得这把锁继续执行,2. 或者线程就保持等待。
而使用重入锁,提供了另一种可能,这就是线程可以被中断。
也就是在等待锁的过程中,程序可以根据需要取消对锁的需求。
下面的例子中,产生了死锁,但得益于锁中断,最终解决了这个死锁:
可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。

可重入锁最大作用(优点)是避免死锁
3.1 例子
线程 thread1 和 thread2 启动后,thread1 先占用 lock1,再占用 lock2;
thread2 反之,先占 lock2,后占 lock1。这便形成 thread1 和 thread2 之间的相互等待。
如果此时,thread2 被中断(interrupt),故 thread2 会放弃对 lock1 的申请,同时释放已获得的 lock2。
这个操作导致 thread1 顺利获得 lock2,从而继续执行下去。
4. 源码方法
1. boolean  nonfairTryAcquire()非公平默认的方式
	同一个线程都是返回true
2.  boolean tryRelease() 只有最后当前线程使用完毕,才返回true
3.  boolean tryAcquire()当前线程用完一次锁,立刻返回
5. 关于可重入锁的问题
5.1我们要优先使用公平锁么
公平性锁保证了锁的获取按照FIFO原则
	( FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器)
而代价是进行大量的线程切换。非公平性锁虽然可能造成线程“饥饿”,
但极少的线程切换,保证了其更大的吞吐量。
5.2 那为什么非公平锁的性能更高?绝对性能高吗?
1.在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。这是影响性能的主要原因

假设线程A持有一个锁,并且线程B请求这个锁。由于锁被A持有,因此B将被挂起。
当A释放锁时,B将被唤醒,因此B会再次尝试获取这个锁
。与此同时,如果线程C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。
这样就是一种双赢的局面:B获得锁的时刻并没有推迟,C更早的获得了锁,并且吞吐量也提高了。
5.3 公平锁如何实现的
公平锁的实现机理在于每次有线程来抢占锁的时候,都会检查一遍有没有等待队列,
如果有等待队列,判断同步队列中当前节点是否有前驱节点,如果有早于当前请求的节点,
   (其实是判断是否有早于当前请求的,如果有,必须等前面的释放后才能获取锁)
则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。
5.4 非公平锁如何实现的
非公平锁在实现的时候多次强调随机抢占:
与公平锁的区别在于新晋获取锁的进程会有多次机会去抢占锁。

ReenTrantLock重入锁和synchronized的区别

相同: 都是可重入锁
区别:
1. 公平性,syn是非公平的,而重入锁ReenTrantLock可以公平和非公平
2. ReenRrantLock 提供了一个能够中断等待线程的机制,即lock.lockInterruptibly()
 	或者说可以在获取锁的同时,中断当前线程
3. ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,
	而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
		
4. synchronize是在JVM层面实现的,系统会监控锁的释放与否。
	lock是代码实现的,需要手动释放,在finally块中释放。可以采用非阻塞的方式获取锁。

猜你喜欢

转载自blog.csdn.net/qq_39455116/article/details/86649901