Lock类(扩展)与Condition类

Lock类(扩展)与Condition类

不提供代码,希望大家动动手指敲敲代码!

Lock类

加锁lock和释放锁unlock

  • lock:加锁
  • unlock:释放锁
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7LWmyLLF-1596111461748)(D:\笔记\博客\images\java\多线程\多线程Lock、Condition\Lock.png)]
结果:
线程2-->1
线程3-->2
线程1-->3

公平锁与非公平锁

简单理解公平与非公平:
如果一个线程组里,能保证每个线程都能拿到锁,那么这个锁就是公平锁。相反,如果保证不了每个线程都能拿到锁,也就是存在有线程饿死,那么这个锁就是非公平锁。

实现原理:
那如何能保证每个线程都能拿到锁呢,队列FIFO是一个完美的解决方案,也就是先进先出,java的ReenTrantLock也就是用队列实现的公平锁和非公平锁。

在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的),当锁没有被持有时,新发出请求的线程和队列里的等待的线程都可以抢占这个锁。所以,它们的差别在于非公平锁会有更多的机会去抢占锁。

公平锁:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IjmbwnJX-1596111461756)(D:\笔记\博客\images\java\多线程\多线程Lock、Condition\公平锁.png)]

结果:哪个先启动,哪个就先获得锁。
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获得了锁

非公平锁:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x9HWFeZF-1596111461760)(D:\笔记\博客\images\java\多线程\多线程Lock、Condition\非公平锁.png)]

结果:无论哪个先启动,新发出请求的线程和队列里的等待的线程都在抢占锁,造成先启动的不一定先得到锁。
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()方法获取当前线程释放是公平锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vKfIo7pG-1596111461764)(D:\笔记\博客\images\java\多线程\多线程Lock、Condition\isFair.png)]


获取当前线程调用lock的次数

​ Java提供了getHoldCount()方法来获取当前线程的锁定个数。所谓锁定个数就是当前线程调用lock方法的次数。一般一个方法只会调用一个lock方法,但是有可能在同步代码中还有调用了别的方法,那个方法内部有同步代码。这样,getHoldCount()返回值就是大于1。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O8UEJBmz-1596111461768)(D:\笔记\博客\images\java\多线程\多线程Lock、Condition\getHoldCount.png)]

结果:
2

Lambda表达式


判断等待锁的情况

  • 获取等待锁的线程数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-czYQi5v7-1596111461769)(D:\笔记\博客\images\java\多线程\多线程Lock、Condition\getQueueLength.png)]

结果:
2
1
0
  • 查询指定的线程是否等待获取此锁定
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n8mm7YtU-1596111461770)(D:\笔记\博客\images\java\多线程\多线程Lock、Condition\getQueueThread.png)]
结果:
true
false
  • 查询是否有线程等待获取此锁定
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5zWrvY4f-1596111461771)(D:\笔记\博客\images\java\多线程\多线程Lock、Condition\getQueueThreads.png)]
结果:
true
true
false

判断持有锁的情况

  • 查询当前线程是否持有锁定
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UJAC8d4E-1596111461773)(D:\笔记\博客\images\java\多线程\多线程Lock、Condition\isHeldByCurrentThread.png)]
结果:
true
false
  • 判断一个锁是不是被线程持有
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5jqnAlJO-1596111461774)(D:\笔记\博客\images\java\多线程\多线程Lock、Condition\isLocked.png)]
结果:
true

实现多种方式加锁

  • 加锁时如果中断则不加锁,进入异常处理

    Lock类提供了多种选择的加锁方法,lockInterruptibly()也可以实现加锁,但是当线程被中断的时候,就会加锁失败,进行异常处理阶段。一般这种情况出现在该线程已经被打上interrupted的标记了。

  • 尝试加锁,如果该锁未被其他线程持有的情况下成功

    Java提供了tryLock()方法来进行尝试加锁,只有该锁未被其他线程持有的基础上,才会成功加锁。

Condition类

​ Condition是Java提供了来实现等待/通知的类,Condition类还提供比wait/notify更丰富的功能,Condition对象是由lock对象所创建的。但是同一个锁可以创建多个Condition的对象,即创建多个对象监视器。这样的好处就是可以指定唤醒线程。notify唤醒的线程是随机唤醒一个。

显示简单的等待/通知:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q1am6FmZ-1596209249205)(D:\笔记\博客\images\java\多线程\ConditionWaitNotifyService.png)]

结果:
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的锁。读写锁分成两个锁,一个锁是读锁,一个锁是写锁。读锁与读锁之间是共享的,读锁与写锁之间是互斥的,写锁与写锁之间也是互斥的。
看下面的读读共享的例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iOTb2iKB-1596209249231)(D:\笔记\博客\images\java\多线程\ReadReadService.png)]

结果:几乎是同时运行
获得读锁A 1596208433167
获得读锁B 1596208433166

下面的例子是写写互斥的例子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-az0fuIhR-1596209249234)(D:\笔记\博客\images\java\多线程\WriteWriteService.png)]

结果:两个线程同步执行代码
获得写锁A 1596208639186
获得写锁B 1596208644224

读写互斥的例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tdUi5peX-1596209249237)(D:\笔记\博客\images\java\多线程\WriteReadService1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xyVdT7xV-1596209249246)(D:\笔记\博客\images\java\多线程\WriteReadService2.png)]

结果:无论是先读还是写,最终都要等另一方释放锁才能继续运行
获得写锁A 1596209019277
获得读锁B 1596209024305
©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页