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

  • 博主简介:想进大厂的打工人
  • 博主主页:@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.mfbz.cn/a/12215.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HyperWorks2021软件安装教程

下载软件 https://www.xsoftnet.com/share/a0004MWyQAg9r.html产品介绍&#xff1a; HyperWorks一款功能强大的开放式架构仿真软件。拥有先进的技术以及高性能、高效和创新的产品&#xff0c;为用户提供了设计、仿真和制造等服务。支持电磁分析设计、材料建模制造、多物理场分…

【数据结构】二叉树链式结构

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;初阶数据结构 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对…

18 隐私模式下面发送 http 请求不成功

前言 是这样的一个情况, 最近 我们服务存在这样的一个问题 是在登录界面, 假设我用户名 或者 密码输入错误, 能够得到真确的结果, 拿到了 正常的 http 响应, 回来 "用户名 或者 密码 不正确 " 但是 假设是在 隐私模式下面, 同样的输入, 同样的服务, 但是结果 不一…

【配电网故障重构SOP】基于二阶锥松弛的加光伏风机储能进行的配电网故障处理和重构【考虑最优潮流】(Matlab代码实现)

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

Java多线程基础学习(一)

1. 创建线程 1.1 通过构造函数&#xff1a;public Thread(Runnable target, String name){} 或&#xff1a;public Thread(Runnable target){} 示例: Thread thread1 new Thread(new MyThread(), "mythread"); class MyThread extends Thread(){public void …

联盟链是虚构的?没有用的?用FISCO BCOS来展示链委员这件事

前言 当前区块链大都使用的是投票决定这种方法&#xff0c;但是如何使现实中的投票转换到区块链中&#xff0c;如何让举手表决变得更加智能&#xff0c;如何让投票透明、安全、权威&#xff0c;这是区块链的一大设计思路&#xff0c;有很多人觉得联盟链是个梦&#xff0c;是个虚…

计算机网络简史

ARPANET的发展 互联网最早的雏形 1931-ARPANET设计 互联网名人堂 1965-packet switching(分包交换) 1969 第一个RFC(Request for Comments)(开始通过APPANET发布)第一个接口信息处理单元&#xff08;Interface Message Processor&#xff09;&#xff08;下图&#xff0c;节…

制造企业该如何选择MES生产管理系统?盘点四大生产管理系统软件

本文将介绍&#xff1a;1、如何选择MES(生产管理系统&#xff09;&#xff1b;2、盘点四款好用的生产管理系统 生产管理系统即MES(Manufacturing Execution System)&#xff0c;制造执行系统。是面向车间生产的管理系统。在产品从工单发出到成品完工的过程中&#xff0c;MES系…

提取图像特征方法总结 是那种很传统的方法~

目录 写在前面 一、SIFT&#xff08;尺度不变特征变换&#xff09; 1.SIFT特征提取的实质 2.SIFT特征提取的方法 3.SIFT特征提取的优点 4.SIFT特征提取的缺点 5.SIFT特征提取可以解决的问题&#xff1a; 二、HOG&#xff08;方向梯度直方图&#xff09; 1.HOG特征提取…

webgl-图形非矩阵旋转

知识拓展 由&#xff08;x1,y1&#xff09;旋转β角度到&#xff08;x2,y2&#xff09; 根据圆极坐标方程 x1 r*cosα y1 r*sinα 可得 x2 r*cos(α β) r*cosα*cosβ - r*sinα*sinβ,因为x1 r*cosα&#xff0c;y1 r*sinα&#xff0c;所以x2 x1*cosβ -y1*sinβ…

Linux 提权学习

提权的目的是获取 root 权限 root 权限可获取 shadow 文件中的密码 Hash&#xff0c;若内网环境中存在「账户/密码复用」的情况&#xff0c;可用于横向扩展 暴力破解 suid 提权 内核漏洞提权 定时任务提权 sudo 提权 第三方服务提权&#xff08;docker、mysql、redis、NFS提权…

【C++】结构体嵌套结构体

目录 1、缘起 2、结构体嵌套结构体 3、总结 1、缘起 结构体嵌套结构体 是一种数据组织方式&#xff0c;就像 俄罗斯套娃 一样&#xff0c;一个数据结构可以包含另一个数据结构。这种嵌套结构使得程序可以更加灵活地处理数据&#xff0c;从而更好地满足复杂的需求。类比生活中…

Can‘‘t connect to MySQL server on localhost (10061)解决方法

首先检查MySQL 服务没有启动》如果没有启动&#xff0c;则要启动这个服务。 有时候安装mysql后使用mysql命令时报错 Cant connect to MySQL server on localhost (10061)&#xff0c;或者用net start mysql 时报服务名无效&#xff0c;一般是因为mysql服务没有启动。 打开 powe…

MySQL中使用IN()查询到底走不走索引?

MySQL中使用IN&#xff08;&#xff09;查询到底走不走索引&#xff1f; 看数据量 EXPLAIN SELECT * from users WHERE is_doctor in (0,1); 很明显没走索引&#xff0c;下面再看一个sql。 EXPLAIN SELECT * from users WHERE is_doctor in (2,1);又走索引了&#xff0c;所以…

day81【leetcode】打家劫舍专题

文章目录前言一、打家劫舍&#xff08;力扣198&#xff09;【相邻两间房不能偷】二、打家劫舍 II&#xff08;力扣213&#xff09;【围成一圈 相邻两间房不能偷】三、打家劫舍 III&#xff08;力扣337&#xff09;【树形DP】每日一题day81&#xff1a;链表中的下一个更大节点&a…

Java:jdk的安装以及hello world

由于本人头发较多&#xff0c;常常被认为是不用功的程序员&#xff1b;故&#xff0c;我来学学Java&#xff0c;希望我变秃了也变强了&#xff01; 首先是java的安装&#xff0c;根据我司java的建议&#xff0c;安装了jdk8与jdk17&#xff01;因为在众多的版本中&#xff0c;只…

《Netty》从零开始学netty源码(三十九)之PoolSubPage的内存分配

目录 PoolSubPage.allocategetNextAvail方法toHandle方法removeFromPool方法 PoolSubPage.allocate 上一篇我们介绍了PoolSubPage的简单知识&#xff0c;当我们需要PoolSubPage的内存时可调用allocate方法查找可分配二进制的位置&#xff0c;具体的源码过程如下&#xff1a; …

vite .env.test环境使用ant design vue ,打包后a-date-picker控件无法选择日期

前端开发后台管理系统&#xff0c;常用的UI库当属Element UI和 Ant Design Vue&#xff0c;但是前段时间遇到一个奇葩问题&#xff0c;在这里记录一下&#xff0c;防止小伙伴们踩坑。 后台系统&#xff0c;大家肯定都用过时间控件&#xff0c;本期我们使用的是ant design vue&…

网络-IP地址(嵌入式学习)

IP地址基本概念IPv4 五类&#xff1a;A B C D E特殊地址子网掩码子网号概念IPv6优势举个栗子基本概念 IP地址是Internet中主机的标识 IP地址&#xff08;Internet Protocol Address 互联网国际地址&#xff09;是一种在Internet上的给主机编址的方式&#xff0c;它主要是为互…

piwigo安装及初步使用

一 摘要 本文主要介绍piwigo 安装及初步使用&#xff0c;nginx \php\mysql 等使用 docker 安装 二 环境信息 2.1 操作系统 CentOS Linux release 7.9.2009 (Core)2.2 piwigo piwigo-13.6.0.zip三 安装 3.1安装资源下载 piwigo 请到官网下载https://piwigo.org 安装步骤也…