AQS和JUC

⼀、ReentrantLock重⼊锁

1.1> 概述

重⼊锁可以完全替代synchronized关键字。在JDK 5 . 0 的早期版本中,重⼊锁的性能远远好于
synchronized,但从JDK 6 . 0 开始,JDK在synchronized上做了⼤量的优化,使得两者的性能差距并
不⼤。重⼊锁对逻辑控制的灵活性要远远好于synchronized。
重⼊锁常⽤⽅法
void lock() :获得锁,如果锁已经被占⽤,则等待。
void lockInterruptibly() :获得锁,但优先响应中断。
boolean tryLock() :尝试获得锁,如果成功,返回true;如果失败则返回false;获得不到锁,则不进⾏等待,⽴即返回。
boolean tryLock(long time, TimeUnit unit) :在给定时间内尝试获得锁。
boolean isHeldByCurrentThread() :判断担⼼线程是否持有锁。
void unlock() :释放锁。
下⾯是使⽤重⼊锁的简单示例:
AQS和JUC_第1张图片
之所以称之为 重⼊锁 ,就是⼀个线程允许反复进⼊。当然,这⾥的反复仅仅局限于⼀个线程;如
果同⼀个线程多次获锁,那么在释放锁的时候,也必须释放相同次数。如果释放锁的次数多,那么
会得到⼀个 java.lang.IllegalMonitorStateException 异常,反之,如果释放锁的次数少,那么相当
于线程还持有这个锁。如下所示:
AQS和JUC_第2张图片

1.2> 中断响应 lockInterruptibly()

如果使⽤synchronized,要么获得锁,要么保持等待。⽽如果使⽤了重⼊锁,则提供了另⼀种可
能,那就是线程可以被中断。也就是在等待锁的过程中,程序可以根据需要取消对锁的请求。即:
如果⼀个线程正在等待锁,那么它依然可以收到⼀个通知,被告知⽆须再等待,可以停⽌⼯作了
可以很好的应对死锁问题。示例如下所示:
AQS和JUC_第3张图片
AQS和JUC_第4张图片

1.3> 锁申请等待限时 tryLock(long time, TimeUnit unit)

除了等待外部通知之外,要避免死锁还有另外⼀种⽅式,就是 限时等待 。以下⾯为例,线程尝试
获得锁,如果没有获得锁,则等待 5 秒钟。如果 5 秒钟之后依然没有获得锁,则返回false,表示获得锁失败。
AQS和JUC_第5张图片
tryLock()⽅法也可以不带参数直接运⾏。在这种情况下,当前线程会尝试获得锁,如果锁并未被其他线程占⽤,则申请锁会成功,并⽴即返回true。如果锁被其他线程占⽤,则 当前线程不会进
⾏等待,⽽是⽴即返回false 。这种模式不会引起线程等待,因此也不会产⽣死锁。
AQS和JUC_第6张图片

1.4> 公平锁和⾮公平锁

在⼤多数情况下,锁的申请都是⾮公平锁。系统只是会从这个锁的等待线程中随机选择⼀个。类似⼤家买票不去排队,乱哄哄的围在售票窗⼝前,售票员忙得焦头烂额,也顾不及谁先谁后,随便找 ⼀个⼈出票就完事⼉了。
AQS和JUC_第7张图片
当⼊参为true时,则采⽤公平锁⽅式。要求系统维护⼀个有序队列,因此公平锁的实现成本⽐较
⾼,性能相对也⾮常低下。因此,默认情况下,锁是⾮公平的。如果没有特别的需求,也不需要使
⽤公平锁。 AQS和JUC_第8张图片

⼆、Condition重⼊锁的搭配类

相同点

Condition和Object的wait()、notify()⽅法的作⽤是⼤致相同的,对应关系如下所示:
Condition.await()--->Object.wait()
Condition.signal()--->Object.notify()
Condition.signalAll()--->Object.notifyAll()

不同点

Object.wait()和Object.notify()⽅法是和Synchronized关键字合作使⽤的;⽽Condition是与
ReentrantLock相关联的。

Condition常⽤⽅法

void await()
会使当前线程等待,同时释放当前锁,当其他线程中使⽤signal()或者signalAll()⽅法时,线程会重新获得锁并继续执⾏。或者当线程被中断是,也能跳出等待。这和Object.wait()⽅法很相似。
void awaitUninterruptibly()
与await()⽅法基本相同,区别是它不会在等待过程中响应中断。
long awaitNanos(long nanosTimeout)
如果nanosTimeout时间内,没有被执⾏signal,则解除等待状态。
boolean await(long time, TimeUnit unit)
如果time时间内,没有被执⾏signal,则解除等待状态。
boolean awaitUntil(Date deadline)
如果deadline时间内,没有被执⾏signal,则解除等待状态。
void signal()
⽤于唤醒⼀个正在等待中的线程。
void signalAll()
⽤于唤醒所有正在等待中的线程。

Condition使⽤示例:

AQS和JUC_第9张图片

三、Semaphore信号量

⼴义上说,Semapore信号量是对锁的⼀种扩展;因为⽆论是内部锁synchronized,还是重⼊锁
ReentrantLock,⼀次都只允许⼀个线程访问某⼀资源,⽽信号量却可以 指定多个线程同时访问某⼀个资源
● 信息量主要提供了⼀下构造函数,必须要指定信号量的准⼊数,即:同时能申请多少个许可
public Semaphore(int permits) ; // permits:准⼊数
public Semaphore(int permits, boolean fair) ; // permits:准⼊数,fair:是否公平获得锁
● 信息量主要⽅法如下所示:
public void acquire() ;
尝试获得⼀个准⼊的许可。若⽆法获得,则线程会等待,直到有线程释放
⼀个许可或者当前线程被中断。
public void acquireUninterruptibly() ;
具有acquire⼀样的功能,但是不响应中断。
public void tryAcquire() ; 尝试获得⼀个许可,如果成功就返回true,失败则返回false。
public void tryAcquire(long timeout, TimeUnit unit) ; 在指定时间内,尝试获得⼀个许可,如果成功就返回true,失败则返回false。
public void release() ;
资源访问结束后,释放⼀个许可。
● 申请了4 个准⼊,循环 10 个线程,那么将会以 4 个线程⼀组为单位进⾏执⾏输出
AQS和JUC_第10张图片

四、ReadWriteLock读写锁

● ReadWriteLock是JDK5 中提供的读写分离锁。它允许多个线程同时读。但是考虑到数据的完整性, 写写操作和读写操作间依然是需要相互等待和持有锁的。读写锁的访问约束情况如下所示:
AQS和JUC_第11张图片
下⾯例⼦中,我们使⽤ 普通锁 ,执⾏读写操作:
AQS和JUC_第12张图片
AQS和JUC_第13张图片
下⾯例⼦中,我们使⽤ 读写锁 (只需要将上⾯例⼦中openRWLock=true即可)执⾏读写操作,如下所示:
AQS和JUC_第14张图片
AQS和JUC_第15张图片
所以,如果在系统中,读操作的次数远远⼤于写操作,那么读写锁就可以发挥最⼤的效果,提升系统的性能。

五、CountDownLatch倒计时器

CountDownLatch是⼀个多线程控制⼯具。⽤来控制线程的等待。设置需要countDown的数量
num,然后每⼀个线程执⾏完毕后,调⽤countDown()⽅法,⽽主线程调⽤await()⽅法执⾏等待,
直到num个⼦线程执⾏了countDown()⽅法 ,则主线程开始继续执⾏。
AQS和JUC_第16张图片

六、CyclicBarrier循环栅栏

CyclicBarrier与CountDownLatch⾮常类似,它⽀持计数器的反复使⽤,CyclicBarrier可以
理解为循环栅栏。CyclicBarrier可以接收⼀个参数作为Runnable barrierAction,每当计数器
⼀次计数完成后——CyclicBarrier.await()时,系统会执⾏的动作。
AQS和JUC_第17张图片
CyclicBarrier.await()⽅法可能会 抛出两种异常 :⼀个是 InterruptedException ,也就是
在等待过程中,线程被中断,应该说这是⼀个⾮常通⽤的异常,⼤部分迫使线程等待的⽅法都可能
会抛出这个异常,使得线程在等待时依然可以响应外部紧急事件。另外⼀个异常则是CyclicBarrier
特有的 BrokenBarrierException ,⼀旦遇到这个异常,则表示当前的CyclicBarrier已经破损
了,可能系统已经没有办法等待所有线程到⻬了。如果继续等待,可能就是徒劳⽆功的,因此就此
结束吧。
AQS和JUC_第18张图片

七、LockSupport线程阻塞⼯具类

LockSupport是⼀个⾮常⽅便实⽤的 线程阻塞⼯具 ,它可以在线程内任意位置让线程阻塞。和
Thread.suspend()相⽐,它弥补了由于resume()在前发⽣,导致线程⽆法继续执⾏的情况。和
Object.wait()⽅法相⽐,它 不需要先获得某个对象的锁 ,也不会抛出InterruptedException
异常。
park()可以阻塞当前线程,其中每⼀个线程都有⼀个许可,该许可 默认为[不可⽤] 。如果该许可
是[可⽤]状态,那么park()⽅法会⽴即返回,消费这个许可,将该许可 变更为[不可⽤] 状态,流
程代码可以继续执⾏。 如果该许可是[不可⽤]状态,那么park()⽅法将会阻塞
unpark⽅法,将指定线程的⼀个许可 变为[可⽤] 状态。如下表所示:
AQS和JUC_第19张图片
示例⼀:先执⾏unpark()⽅法再执⾏park()⽅法,也不会造成永久卡死线程。如下所示:
AQS和JUC_第20张图片
示例⼆:LockSupport.park()还能⽀持中断。但是它 不会抛InterruptedException异常
它只会默默的返回,但是我们可以从Thread.interrupted()等⽅法获得中断标记。
AQS和JUC_第21张图片

你可能感兴趣的:(java,开发语言)