ReentrantLock介绍
java多线程中可以使用synchronized关键字来实现线程间同步互斥,但在jdk1.5中新增加了ReentrantLock类也能实现同样的效果。
ReentrantLock:表示重入锁,它是唯一一个实现了Lock接口的类。重入锁指的是 线程在获得锁之后,再次获取该锁不需要阻塞,而是直接关联一次计数器增加重入次;
Lock常用方法
ReentrantLock.lock() 这个是reentrantLock获取锁的入口
public void lock() { sync.lock(); }
sync实际上是一个抽象的静态内部类,它继承了AQS来实现重入锁的逻辑,我们前面说过AQS是一个同步队列,它能够实现线程的阻塞以及唤醒,但它并不具备 业务功能,所以在不同的同步场景中,会继承AQS来实现对应场景的功能 Sync有两个具体的实现类,
什么是AQS?
AQS即AbstractQueuedSynchronizer,是一个用于构建锁和同步器的框架。它能降低构建锁和同步器的工作量,还可以避免处理多个位置上发生的竞争问题。在基于AQS构建的同步器中,只可能在一个时刻发生阻塞,从而降低上下文切换的开销,并提高吞吐量
AQS支持独占锁(exclusive)和共享锁(share)两种模式。
- 独占锁:只能被一个线程获取到(Reentrantlock)
- 共享锁:可以被多个线程同时获取(CountDownLatch,ReadWriteLock).
分别是:
NofairSync:非公平锁,可以存在抢占锁的功能,也就是说不管当前队列上是否存在其他 线程等待,新线程都有机会抢占锁
FailSync:公平锁,所有线程严格按照FIFO来获取锁
公平锁示例
谁等的时间最长,谁就先获取锁
public class ReentrantLockTest { static Lock lock = new ReentrantLock(true); public static void main(String[] args) throws InterruptedException { for(int i=0;i<5;i++){ new Thread(new ThreadDemo(i)).start(); } } static class ThreadDemo implements Runnable { Integer id; public ThreadDemo(Integer id) { this.id = id; } @Override public void run() { try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0;i<2;i++){ lock.lock(); System.out.println("获得锁的线程:"+id); lock.unlock(); } } } }
非公平锁示例
非公平锁那就随机的获取,谁运气好,cpu时间片轮到哪个线程,哪个线程就能获取锁
static Lock lock = new ReentrantLock(false);//参数为false,当然我们也可以不写,默认就是false
ReentrantLock和synchronized对比
(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。
总结
ReentrantLock是一种可重入的,可实现公平性的互斥锁,它的设计基于AQS框架,可重入和公平性的实现逻辑都不难理解,每重入一次,state就加1,当然在释放的时候,也得一层一层释放。至于公平性,在尝试获取锁的时候多了一个判断:是否有比自己申请早的线程在同步队列中等待,若有,去等待;若没有,才允许去抢占。