Lock类(扩展)与Condition类
不提供代码,希望大家动动手指敲敲代码!
Lock类
加锁lock和释放锁unlock
- lock:加锁
- unlock:释放锁
结果:
线程2-->1
线程3-->2
线程1-->3
公平锁与非公平锁
简单理解公平与非公平:
如果一个线程组里,能保证每个线程都能拿到锁,那么这个锁就是公平锁。相反,如果保证不了每个线程都能拿到锁,也就是存在有线程饿死,那么这个锁就是非公平锁。
实现原理:
那如何能保证每个线程都能拿到锁呢,队列FIFO是一个完美的解决方案,也就是先进先出,java的ReenTrantLock也就是用队列实现的公平锁和非公平锁。
在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的),当锁没有被持有时,新发出请求的线程和队列里的等待的线程都可以抢占这个锁。所以,它们的差别在于非公平锁会有更多的机会去抢占锁。
公平锁:
结果:哪个先启动,哪个就先获得锁。
Thread-6启动
Thread-2启动
Thread-0启动
Thread-6获得了锁
Thread-0获得了锁
Thread-2获得了锁
Thread-4启动
Thread-4获得了锁
Thread-9启动
Thread-9获得了锁
Thread-7启动
Thread-7获得了锁
Thread-5启动
Thread-5获得了锁
Thread-1启动
Thread-1获得了锁
Thread-3启动
Thread-3获得了锁
Thread-8启动
Thread-8获得了锁
非公平锁:
结果:无论哪个先启动,新发出请求的线程和队列里的等待的线程都在抢占锁,造成先启动的不一定先得到锁。
Thread-1启动
Thread-8启动
Thread-9启动
Thread-3启动
Thread-5启动
Thread-6启动
Thread-1获得了锁
Thread-5获得了锁
Thread-3获得了锁
Thread-7启动
Thread-4启动
Thread-2启动
Thread-0启动
Thread-7获得了锁
Thread-6获得了锁
Thread-4获得了锁
Thread-9获得了锁
Thread-2获得了锁
Thread-8获得了锁
Thread-0获得了锁
优缺点:
非公平锁性能高于公平锁性能。首先,在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。而且,非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。
使用场景
使用场景的话呢,其实还是和他们的属性一一相关,如果业务中线程占用(处理)时间要远长于线程等待,那用非公平锁其实效率并不明显,但是用公平锁会给业务增强很多的可控制性。
此外,java还提供了isFair()方法获取当前线程释放是公平锁
获取当前线程调用lock的次数
Java提供了getHoldCount()
方法来获取当前线程的锁定个数。所谓锁定个数就是当前线程调用lock方法的次数。一般一个方法只会调用一个lock方法,但是有可能在同步代码中还有调用了别的方法,那个方法内部有同步代码。这样,getHoldCount()
返回值就是大于1。
结果:
2
判断等待锁的情况
- 获取等待锁的线程数
结果:
2
1
0
- 查询指定的线程是否等待获取此锁定
结果:
true
false
- 查询是否有线程等待获取此锁定
结果:
true
true
false
判断持有锁的情况
- 查询当前线程是否持有锁定
结果:
true
false
- 判断一个锁是不是被线程持有
结果:
true
实现多种方式加锁
-
加锁时如果中断则不加锁,进入异常处理
Lock类提供了多种选择的加锁方法,
lockInterruptibly()
也可以实现加锁,但是当线程被中断的时候,就会加锁失败,进行异常处理阶段。一般这种情况出现在该线程已经被打上interrupted的标记了。 -
尝试加锁,如果该锁未被其他线程持有的情况下成功
Java提供了
tryLock()
方法来进行尝试加锁,只有该锁未被其他线程持有的基础上,才会成功加锁。
Condition类
Condition是Java提供了来实现等待/通知的类,Condition类还提供比wait/notify更丰富的功能,Condition对象是由lock对象所创建的。但是同一个锁可以创建多个Condition的对象,即创建多个对象监视器。这样的好处就是可以指定唤醒线程。notify唤醒的线程是随机唤醒一个。
显示简单的等待/通知:
结果:
await的时间为-->1596207769350
sign的时间为-->1596207769452
await的时间为-->1596207769452
condition对象通过lock.newCondition()
来创建,用condition.await()
来实现让线程等待,是线程进入阻塞。用condition.signal()
来实现唤醒线程。唤醒的线程是用同一个conditon对象调用await()
方法而进入阻塞。并且和wait/notify一样,await()和signal()也是在同步代码区内执行。
Condition类的其他功能
和wait类提供了一个最长等待时间,awaitUntil(Date deadline)
在到达指定时间之后,线程会自动唤醒。但是无论是await或者awaitUntil,当线程中断时,进行阻塞的线程会产生中断异常。Java提供了一个awaitUninterruptibly
的方法,使即使线程中断时,进行阻塞的线程也不会产生中断异常。
读写锁
Lock类除了提供了ReentrantLock
的锁以外,还提供了ReentrantReadWriteLock
的锁。读写锁分成两个锁,一个锁是读锁,一个锁是写锁。读锁与读锁之间是共享的,读锁与写锁之间是互斥的,写锁与写锁之间也是互斥的。
看下面的读读共享的例子:
结果:几乎是同时运行
获得读锁A 1596208433167
获得读锁B 1596208433166
下面的例子是写写互斥的例子
结果:两个线程同步执行代码
获得写锁A 1596208639186
获得写锁B 1596208644224
读写互斥的例子:
结果:无论是先读还是写,最终都要等另一方释放锁才能继续运行
获得写锁A 1596209019277
获得读锁B 1596209024305