synchronized及JUC显式locks 使用原理解析
作者:Applehope 发布时间:2023-08-05 03:28:41
一、synchronized 有不足
新事物的出现要不是替代老事物,要么就是对老事物的补充
JUC 的 locks 就是对 synchronized 的补充
从开始竞争锁 到拿到锁这段时间,调用线程一直是阻塞状态,啥都干不了
已经占有的资源也释放不了,别的线程也无法获得;这种不可抢占的情况,更容易带来死锁(死锁产生的原理就是:我占着你要用的资源不给你,你却不能抢)
只有一个条件变量(条件等待队列),用于线程间的协调、通信
二、改进意见
采用更多的措施以避免死锁 :
让不能抢占 变为 可抢占,占用部分资源的线程进一步申请其他资源时
如果能快速申请到,就申请
如果不能快速申请到,可以主动释放它占有的资源
使用者自己可创建多个条件变量,用于线程间的协调、通信。
三、可抢占的方 *
3.1 能够响应中断
synchronized
的问题是:持有锁 A 后,如果尝试获取锁 B 失败,那么线程就进入阻塞状态,一旦发生死锁,就没有任何机会来唤醒阻塞的线程。但如果阻塞状态的线程能够响应中断信号,也就是说当我们给阻塞的线程发送中断信号的时候,能够唤醒它,那它就有机会释放曾经持有的锁 A。这样就破坏了不可抢占条件了。
3.2 支持超时
如果线程在一段时间之内没有获取到锁,不是进入阻塞状态,而是返回一个错误,那这个线程也有机会释放曾经持有的锁。这样也能破坏不可抢占条件。
3.3 非阻塞地获取锁
如果尝试获取锁失败,并不进入阻塞状态,而是直接返回,那这个线程也有机会释放曾经持有的锁。这样也能破坏不可抢占条件。
四、可抢占的实现 - JUC 的 locks
// 支持中断的API
void lockInterruptibly() throws InterruptedException;
// 支持超时的API
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 支持非阻塞获取锁的API
boolean tryLock();
五、多个 Condition(条件变量或条件等待队列)
等效于管程中的条件变量,条件变量用于线程间的同步。通过java.util.concurrent.locks.Lock#newCondition()
来创建,每个 Condition
都具有一个 waitSet
;这样锁对象就具备了多个waitSet
; Condition
提供了以下方法:用于线程的协作(线程等待/激活)。
//线程加入此条件变量的等待队列;类似Object.wait();使用时也要放到while循环体内。
java.util.concurrent.locks.Condition#await()
java.util.concurrent.locks.Condition#awaitUninterruptibly()
java.util.concurrent.locks.Condition#awaitNanos()
java.util.concurrent.locks.Condition#await(long, java.util.concurrent.TimeUnit)
java.util.concurrent.locks.Condition#awaitUntil()
//激活此条件变量中的一个线程;类似Object.notify()
java.util.concurrent.locks.Condition#signal()
//激活此条件变量中的所有线程;类似Object.notifyAll()
java.util.concurrent.locks.Condition#signalAll()
java doc 示例:一个有界缓冲,两个条件等待队列,分别被生产者线程和消费者线程来使用。
生产者线程在 notFull 条件队列中等待;意思为生产者线程要阻塞等待,直到 有界缓冲不是满的,才能 put
消费者线程在 notEmpty 条件队列中等待;意思为消费者线程要阻塞等待,直到有界缓冲不是空的,才能 take
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
六、JUC中locks的使用规范
6.1 保证锁的释放
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
Lock lock = ...;
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
6.2 循环体中使用 await()
while (XXX)
condition.await();
6.3 同步代码块中使用 await()
必须先持有锁
l.lock();
try {
...
while (XXX)
condition.await();
...
} finally {
l.unlock();
}
来源:https://juejin.cn/post/7181027840932970554
猜你喜欢
- 本文实例讲述了Android中断线程的处理方法。分享给大家供大家参考。具体方法如下:我现在对一个用户注册的功能1.用ProgressDial
- 1、一个示例回顾Future一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。JDK5新增了Future接口,用于描述一个异步计
- 背景在接口请求过程中,传递json对象,springboot转换为实体VO对象后,所有属性都为null。post请求:后台接收请求:当时就懵
- 合成聚合复用原则合成复用原则又称为组合/聚合复用原则(Composition/Aggregate Reuse Principle, CARP
- 1. 并行和并发有什么区别?并行:多个处理器或多核处理器同时处理多个任务。并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执
- 自动登录是我们在软件开发时一个非常常见的功能,例如我们登录 QQ 邮箱:很多网站我们在登录的时候都会看到类似的选项,毕竟总让用户输入用户名密
- 什么是JMMJMM全称Java Memory Model, 中文翻译Java内存模型,一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问
- 静态库和动态库的区别1、静态库的扩展名一般为".a"或者".lib";动态库的扩展名一般为"
- 我们都知道单精度浮点数(Single,float,Real)由32位0或1组成,它具体是如何来的。浮点数的32位N=1符号位(Sign)+8
- 之前我们在使用vue进行 h5 表单录入的过程中,遇到了Android软键盘弹出,覆盖 h5页面 输入框 问题,在此进行回顾并分享给大家:系
- SpringCloud 整合ribbon的时候出现了这个问题java.lang.IllegalStateException: No inst
- 引言在高并发的场景下,异步是一个极其重要的优化方向。前段时间,生产环境发生一次事故,笔者认为事故的场景非常具备典型性 。写这篇文章,笔者想和
- 如何高效地进行数组复制?如果把一个变量值复制给另外一个数组变量,那么2个变量指向托管堆上同一个引用。如果想在托管堆上创建另外的一份数组实例,
- 在前面的《基于任务的异步编程模式(TAP)》文章中讲述了.net 4.5框架下的异步操作自我实现方式,实际上,在.net 4.5中部分类已实
- 格式要求:SU MO TU WE TH FR SA &nb
- java 进制转换实例详解十进制转成十六进制:  
- SQLite是Android自带的关系型数据库,是一个基于文件的轻量级数据库。Android提供了3种操作数据的方式,SharedPrefe
- 1.最常用的方法是创建一个计数器,判断是否遇到‘\0',不是'\0'指针就往后加一。int my_strlen(co
- idea spring Initializr创建项目勾选项目所需要的依赖pom.xml文件会加载勾选的依赖,也可以不勾选后面通过自己常用的p
- 多数据源创建数据库CREATE DATABASE mybatis_plus_1;USE mybatis_plus_1;CREATE TABL