【JavaEE】关于synchronized总结-Callable用法及JUC的常见问题

news2024/11/13 8:58:13

  • 博主简介:想进大厂的打工人
  • 博主主页:@xyk:
  • 所属专栏: JavaEE初阶

synchronized原理是什么?synchronized到底有什么特点,synchronized的锁策略是什么,是怎么变化的呢?本篇文章总结出, Synchronized 具有以下特性,加锁工作过程,锁消除,锁粗化,Callable接口的用法,JUC的常见问题~~


目录

文章目录

一、synchronized的基本特点

二、synchronized的关键策略——锁升级

 三、其他的优化操作

3.1 锁消除

3.2 锁粗化

四、JUC(java.util.concurrent) 的常见类

4.1 Callable的用法

4.2 ReentrantLock

4.3 如何选择使用哪个锁?

五、信号量 Semaphore

六、CountDownLatch

七、相关面试题

7.1 线程同步的方式有哪些?

7.2 为什么有了 synchronized 还需要 juc 下的 lock?

7.3 AtomicInteger 的实现原理是什么?

7.4 信号量听说过么?之前都用在过哪些场景下?


一、synchronized的基本特点

结合上次写的锁策略文章,我们就可以总结出,synchronized具有一下特性:

1. 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁.
2. 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁
3. 实现轻量级锁的时候大概率用到的自旋锁策略,重量级锁基于挂起等待锁实现
4. 是一种不公平锁
5. 是一种可重入锁
6. 不是读写锁

二、synchronized的关键策略——锁升级

synchronized工作过程,具体讨论下synchronized里面都做了什么:

synchronized的关键策略:锁升级

刚开始加锁,第一个尝试加锁的线程, 优先进入偏向锁状态,那么什么是偏向锁?

 

偏向锁不是真的 "加锁", 只是给对象头中做一个 "偏向锁的标记", 记录这个锁属于哪个线程;
如果后续没有其他线程来竞争该锁, 那么就不用进行其他同步操作了(避免了加锁解锁的开销),


如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别
当前申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进入一般的轻量级锁状态

偏向锁本质上相当于 "延迟加锁" . 能不加锁就不加锁, 尽量来避免不必要的加锁开销.
但是该做的标记还是得做的, 否则无法区分何时需要真正加锁

 

我来举个例子再理解一下:

假设,有个妹子看上了个小哥哥,主动出击,此时有个问题,如果把他拿下了,让他做男票了~~过一段时间,万一腻了的话,想换个男朋友,成本就高了!!

更高效的方法,就是不和他挑明关系,“无情侣之名,有情侣之实”,这样就很轻量,很高效,如果什么时候想换人,直接给他说,咱们只是普通朋友,此时更换男朋友的成本就低了~~

假设妹子在和小哥哥暧昧的过程中,别的妹子,听说这个小哥哥单身,也想过来试试,此时妹子就发现,存在潜在威胁~~于是就立即和小哥哥挑明关系,并且朋友圈宣誓!!此时别的妹子,就只能阻塞等待了~~

偏向锁,只是先让线程针对锁有个标记,如果没有竞争就不加锁,有竞争再升级为真的锁(轻量级锁),此时别的线程只能等待,既保证了效率,又保证了线程安全!!
 

自旋锁 :

随着其他线程进入竞争, 偏向锁状态被消除, 进入轻量级锁状态(自适应的自旋锁)

遇到锁竞争,就是自旋锁(轻量级锁),速度是快,但是消耗大量cpu,自旋的时候,cpu 是快速空转的~~

此处的轻量级锁就是通过 CAS 来实现

  • 通过 CAS 检查并更新一块内存 (比如 null => 该线程引用)
  • 如果更新成功, 则认为加锁成功
  • 如果更新失败, 则认为锁被占用, 继续自旋式的等待(并不放弃 CPU)

如果当前锁竞争比较激烈,比如,10个线程,竞争1个锁,1个竞争上,另外9个等待~~

既然这么多都在自选,cpu的消耗就非常大,既然如此,就升级成重量级锁,在内核里进行阻塞等待(意味着线程要暂时放弃cpu,由内核进行后续调度)

 三、其他的优化操作

3.1 锁消除

非必要不加锁!!

编译器+JVM 判断当前代码是否是多线程执行/锁是否可消除. 如果可以, 就直接再编译过程中自动把锁消除~

有些应用程序的代码中, 用到了 synchronized, 但其实没有在多线程环境下. (例如 StringBuffer)

StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");

此时每个 append 的调用都会涉及加锁和解锁. 但如果只是在单线程中执行这个代码, 那么这些加
锁解锁操作是没有必要的, 白白浪费了一些资源开销;

此时编译器就会自动把锁消除掉~~

不是说,写了synchronized就一定线程安全,也不是不写synchronized就一定线程安全

3.2 锁粗化

跟锁的粒度有关系,那么什么是锁粒度?

锁的粒度,就是synchronized代码块,包含代码的多少(代码越多,粒度越粗,代码越少,粒度越细)

一般情况下,写代码希望锁的粒度更小一点(串行执行的代码少,并行执行的代码多)

但是如果某个场景,要频繁加锁/解锁,此时编译器就可以把这个操作优化成一个更粗粒度的锁

 

 每次加锁解锁,都要有开销~~尤其是释放锁之后,重新加锁,还需要重新竞争~~

举个例子立即锁粗化:

滑稽老哥当了领导, 给下属交代工作任务:
方式一:

  • 打电话, 交代任务1, 挂电话.
  • 打电话, 交代任务2, 挂电话.
  • 打电话, 交代任务3, 挂电话.

方式二:

  • 打电话, 交代任务1, 任务2, 任务3, 挂电话.

显然, 方式二是更高效的方案

可以看到, synchronized 的策略是比价复杂的, 在背后做了很多事情, 目的为了让程序猿哪怕啥都不懂,也不至于写出特别慢的程序

JVM 开发者为了 Java 程序猿操碎了心

四、JUC(java.util.concurrent) 的常见类

4.1 Callable的用法

Callable 是一个 interface . 相当于把线程封装了一个 "返回值". 方便程序猿借助多线程的方式计算结果;

Callable的用法非常类似Runnable,描述了一个任务~~一个线程要做什么

Runnable通过run方法描述,返回类型是void
Callable通过call方法,有返回值

代码示例:创建线程计算 1 + 2 + 3 + ... + 1000

  • 创建一个匿名内部类,实现 Callable 接口. Callable 带有泛型参数.泛型参数表示返回值的类型.
  • 重写 Callable 的 call 方法, 完成累加的过程. 直接通过返回值返回计算结果.
  • 把 callable 实例使用 FutureTask 包装一下.
  • 创建线程, 线程的构造方法传入 FutureTask . 此时新线程就会执行 FutureTask 内部的 Callable 的call 方法, 完成计算. 计算结果就放到了 FutureTask 对象中.
  • 在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果.
Callable<Integer> callable = new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 1000; i++) {
            sum += i;
        }
        return sum;
     }
};
    FutureTask<Integer> futureTask = new FutureTask<>(callable);
    Thread t = new Thread(futureTask);
    t.start();
    int result = futureTask.get();
    System.out.println(result);

 

 futureTask包含了一个get方法,就是用来取结果的方法!!

如果保证,调用get的时候,t线程的call方法是执行完毕了呢?

 get方法和join方法类似,都是会阻塞等待!!

4.2 ReentrantLock

synchronized 关键字, 是基于代码块的方式来控制加锁解锁的

可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全
ReentrantLock 也是可重入锁. "Reentrant" 这个单词的原意就是 "可重入“

ReentrantLock 则是提供了lock和unlock独立的方法,来进行加锁解锁~~

ReentrantLock 的用法:
lock(): 加锁, 如果获取不到锁就死等.
trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁.
unlock(): 解锁

ReentrantLock lock = new ReentrantLock();
-----------------------------------------
lock.lock();
try {
    // working
} finally {
    lock.unlock()
}

虽然大部分情况下,使用synchronized就足够了,ReentrantLock也是一个重要的补充!!

ReentrantLock 和 synchronized 的区别:

1.synchronized只是加锁和解锁,加锁的时候如果发现锁被占用,只能阻塞等待!!

ReentrantLock还提供一个方法tryLock方法:

如果加锁成功,没什么特殊的,就是普通的加锁

如果加锁失败,不会阻塞,直接返回false!!

2.synchronized是一个非公平锁(概率均等,不遵守先来后到)

ReentrantLock提供了公平和非公平俩种工作模式(在构造方法中,传入true开启公平锁)

3.synchronized搭配wait notify进行唤醒等待,如果多个线程wait同一个对象,notify随机唤醒一个线程

ReentrantLock则是搭配Condition这个类,这个类也能起到等待通知,可能功能更强大!!

4.3 如何选择使用哪个锁?

锁竞争不激烈的时候, 使用 synchronized, 效率更高, 自动释放更方便.
锁竞争激烈的时候, 使用 ReentrantLock, 搭配 trylock 更灵活控制加锁的行为, 而不是死等.
如果需要使用公平锁, 使用 ReentrantLock
 

五、信号量 Semaphore

信号量, 用来表示 "可用资源的个数". 本质上就是一个计数器

理解信号量
可以把信号量想象成是停车场的展示牌: 当前有车位 100 个. 表示有 100 个可用资源.
当有车开进去的时候, 就相当于申请一个可用资源, 可用车位就 -1 (这个称为信号量的 P 操作)
当有车开出来的时候, 就相当于释放一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作)
如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源

Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用
代码示例:

Semaphore semaphore = new Semaphore(4);
Runnable runnable = new Runnable() {
@Override
public void run() {
    try {
        System.out.println("申请资源");
        semaphore.acquire();
        System.out.println("我获取到资源了");
        Thread.sleep(1000);
        System.out.println("我释放资源了");
        semaphore.release();
    } catch (InterruptedException e) {
        e.printStackTrace();
        }
    }
};
for (int i = 0; i < 20; i++) {
    Thread t = new Thread(runnable);
    t.start();
}

六、CountDownLatch

同时等待 N 个任务执行结束

好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩

构造 CountDownLatch 实例, 初始化 10 表示有 10 个任务需要完成.
每个任务执行完毕, 都调用 latch.countDown() . 在 CountDownLatch 内部的计数器同时自减.主线程中使用 latch.await(); 阻塞等待所有任务执行完毕. 相当于计数器为 0 了

七、相关面试题

7.1 线程同步的方式有哪些?

synchronized, ReentrantLock, Semaphore 等都可以用于线程同步

7.2 为什么有了 synchronized 还需要 juc 下的 lock?

以 juc 的 ReentrantLock 为例,

  • synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活,
  • synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃.
  • synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个true 开启公平锁模式.
  • synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一个随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程

7.3 AtomicInteger 的实现原理是什么?

class AtomicInteger {
private int value;
public int getAndIncrement() {
int oldValue = value;
while ( CAS(value, oldValue, oldValue+1) != true) {
oldValue = value;
}
return oldValue;
}
}

7.4 信号量听说过么?之前都用在过哪些场景下?

  • 信号量, 用来表示 "可用资源的个数". 本质上就是一个计数器.
  • 使用信号量可以实现 "共享锁", 比如某个资源允许 3 个线程同时使用, 那么就可以使用 P 操作作为加锁, V 操作作为解锁, 前三个线程的 P 操作都能顺利返回, 后续线程再进行 P 操作就会阻塞等待,直到前面的线程执行了 V 操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/417303.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java|golang】1041. 困于环中的机器人

在无限的平面上&#xff0c;机器人最初位于 (0, 0) 处&#xff0c;面朝北方。注意: 北方向 是y轴的正方向。 南方向 是y轴的负方向。 东方向 是x轴的正方向。 西方向 是x轴的负方向。 机器人可以接受下列三条指令之一&#xff1a; “G”&#xff1a;直走 1 个单位 “L”&…

Markdown 语法大全

Markdown是一种轻量级标记语言&#xff0c;常用于撰写博客、文档、论文等。它可以让你使用易读易写的纯文本格式来编写文档&#xff0c;然后通过转换成有效的HTML文档进行发布。以下是Markdown常用的语法&#xff1a; 这里写目录标题标题列表引用一级引用嵌套引用粗体和斜体删除…

技术复盘(1)--redis

技术复盘--redis技术复盘(1)--redis资料地址准备工作发展史redis-windowsredis-windows-说明redis-centos7安装jdk安装redisredis-key基本命令redis-string命令redis-list命令redis-set命令redis-hash命令redis-zset命令redis-geospatial命令redis-hyperloglog命令redis-bitmap…

【Linux驱动开发】024 INPUT子系统

一、前言 按键、鼠标、键盘、触摸屏等都属于输入(input)设备&#xff0c;Linux 内核为此专门做了一个叫做 input子系统的框架来处理输入事件。输入设备本质上还是字符设备&#xff0c;只是在此基础上套上了 input 框架&#xff0c;用户只需要负责上报输入事件&#xff0c;比如…

文本聚类与摘要,让AI帮你做个总结

你好&#xff0c;我是徐文浩。 上一讲里&#xff0c;我们用上了最新的ChatGPT的API&#xff0c;注册好了HuggingFace的账号&#xff0c;也把我们的聊天机器人部署了出去。希望通过这个过程&#xff0c;你对实际的应用开发过程已经有了充足的体验。那么这一讲里&#xff0c;我们…

[目标识别-论文笔记]Object Detection in Videos by Short and Long Range Object Linking

文章标题&#xff1a;2018_Cite13_Tang——Object Detection in Videos by Short and Long Range Object Linking 这篇论文也被叫做“2019_Cite91_TPAMI_Tang——Object Detection in Videos by High Quality Object Linking” 如果这篇博客对你有帮助&#xff0c;希望你 点赞…

ES索引库操作

文章目录1、对索引库的操作&#xff1a;创建、删除、查看2、文档操作3、 RestClient操作索引库4、利用RestClient实现文档的CRUD5、 批量导入功能有了索引库相当于数据库database&#xff0c;而接下来&#xff0c;就是需要索引库中的类型了&#xff0c;也就是数据库中的表&…

nssctf web入门(1)

这里通过nssctf的题单web安全入门来写&#xff0c;会按照题单详细解释每题。题单在NSSCTF中。 想入门ctfweb的可以看这个系列&#xff0c;之后会一直出这个题单的解析&#xff0c;题目一共有28题&#xff0c;打算写10篇。 [SWPUCTF 2021 新生赛]jicao [SWPUCTF 2021 新生赛]j…

RL4RS,离线强化学习,无模型强化学习等等资源汇总

发现好文章&#xff1a; 强化学习推荐系统综述&#xff1a;Reinforcement Learning based Recommender Systems: A Survey 强化学习图鉴&#xff5c;你与最优策略之间&#xff0c;可能还差一本离线强化学习秘籍 科学应用强化学习创新论文洞察 https://hub.baai.ac.cn/view/18…

【论文精读】PP-YOLOE: An evolved version of YOLO

文章目录前言一、可扩展的 Backbone 和 Neck二、更高效的标签分配策略 TAL (Task Alignment Learning)三、更简洁有效的 ET-Head (Efficient Task-aligned Head)前言 百度飞桨团队发布了 PP-YOLOE&#xff0c;与其他 YOLO 系列算法相比&#xff0c;其具有更强的性能、更丰富灵…

8.2 正态总体的参数的检验

学习目标&#xff1a; 如果我要学习正态总数的参数检验&#xff0c;我会按照以下步骤进行学习&#xff1a; 学习正态分布的基本知识&#xff1a;正态分布是统计学中非常重要的概率分布之一&#xff0c;掌握其基本知识包括概率密度函数、期望值、方差、标准差等是非常重要的。 …

Prometheus - Grafana 监控 MySQLD Linux服务器 demo版

目录 首先是下载Prometheus 下载和安装 配置Prometheus 查看监控数据 监控mysql demo 部署 mysqld_exporter 组件 配置 Prometheus 获取监控数据 -------------------------------------- 安装和使用Grafana 启动Grafana -------------------------------------- 配…

MySQL5.5安装图解

一、MYSQL的安装 &#xff11;、打开下载的mysql安装文件mysql-5.5.27-win32.zip&#xff0c;双击解压缩&#xff0c;运行“setup.exe” &#xff12;、选择安装类型&#xff0c;有“Typical(默认)”、“Complete(完全)”、“Custom(用户自定义)”三个选项&#xff0c;选择“Cu…

VSD Viewer for Mac,Visio绘图文件阅读器

VSD Viewer for Mac版是mac上一款非常强大的Visio绘图文件阅读器&#xff0c;它为打开和打印Visio文件提供了简单的解决方案。可以显示隐藏的图层&#xff0c;查看对象的形状数据&#xff0c;预览超链接。还可以将Visio转换为包含图层&#xff0c;形状数据和超链接的PDF文档。 …

【状态估计】基于增强数值稳定性的无迹卡尔曼滤波多机电力系统动态状态估计(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Linux 进程管理之四大名捕

一、四大名捕 四大名捕&#xff0c;最初出现于温瑞安创作的武侠小说&#xff0c;是朝廷中正义力量诸葛小花的四大徒弟&#xff0c;四人各怀绝技&#xff0c;分别是轻功暗器高手 “无情”、内功卓越的高手“铁手”、腿功惊人的“追命” 和剑法一流的“冷血”。 本文四大名捕由…

【Unity入门】11.脚本控制物体旋转

【Unity入门】脚本控制物体旋转 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity入门系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;控制物体自转 &#xff08;1&#xff09;创建RotateLogic脚本 上一篇文章我们学习了如何在脚本中获取物体对象…

【前端工具】使用真机在chrome远程调试

手机端需要做的事 手机上下载chrome浏览器 手机开启“开发者模式” 具体步骤各个品牌手机不太一样&#xff0c;华为手机为例&#xff1a; 打开手机上的 “设置” 图标&#xff0c; 进入最下方 “系统” 选项&#xff0c; 再点击最上方 “关于手机”&#xff0c; 接着连续点击 …

(十八)排序算法-计数排序

1 基本介绍 1.1 概述 计数排序是一个非基于比较的排序算法&#xff0c;元素从未排序状态变为已排序状态的过程&#xff0c;是由额外空间的辅助和元素本身的值决定的。该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时&#xff0c;它的复杂度…

一文解读基于PaddleSeg的钢筋长度超限监控方案

项目背景 钢铁厂生产钢筋的过程中会存在部分钢筋长度超限的问题&#xff0c;如果不进行处理&#xff0c;容易造成机械臂损伤。因此&#xff0c;需要通过质检流程&#xff0c;筛选出存在长度超限问题的钢筋批次&#xff0c;并进行预警。传统的处理方式是人工核查&#xff0c;该方…