synchronized的原理和Callable接口

news2024/11/19 0:28:29

目录

♫synchronized原理

♪锁升级

♪锁优化

 

♫Callable接口


♫synchronized原理

我们知道synchronized锁可以控制多个线程对共享资源的访问,两个线程针对同一变量访问就会产生阻塞等待。而synchronized锁并不是一成不变的,它会根据情况进行一次升级。

♪锁升级

JVM 将synchronized锁分为 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 四种状态。
♩偏向锁

第一个尝试加锁的线程就会优先进入偏向锁状态。偏向锁并不是真正的加锁,只是给线程一个偏向锁的标记,如果一直没有其他线程尝试加锁,则等到synchronized执行完取消偏向锁标记即可,如果有其他线程尝试加锁,则再真在进行加锁,让其它线程进入阻塞等待。(类似前面介绍过的懒汉模式,能不加锁则不加)

举个例子:假设男主是一个锁, 女主是一个线程。如果只有这一个线程来使用这个锁,那么男主女主即使不领证结婚(避免了高成本操作),也可以一直幸福的生活下去。但是女配出现了,也尝试竞争男主,此时不管领证结婚这个操作成本多高,女主也势必要把这个动作完成了,让女配死心。

♩轻量级锁

有其他线程尝试加锁 ,偏向锁状态就被消除, 进入了轻量级锁状态 ( 自适应的自旋锁 )。这里的轻量级锁就是通过CAS实现的自旋锁,由于自旋锁需占用CPU不断尝试加锁,如果一直自旋会很浪费CPU资源,故synchronized会通过一个计数器,记录自旋次数,一定超出某个阈值就不再自旋。

♩重量级锁

如果竞争进一步激烈 , 自旋不能快速获取到锁状态 , 就会转换为重量级锁。这里的重量级锁就是使用OS提供的 mutex 锁,此时其它线程尝试加锁就是在内核态来判断锁是否被占用,如果没被占用则返回用户态,否则就进入等待队列,等待操作系统唤醒

♪锁优化

♩锁消除
锁消除是指编译器自动判断程序中某些代码块不需要同步保护,因而消除这些代码块中的锁,从而提高程序的执行效率。
锁消除的主要依据是:如果程序中某些代码块不会被多个线程同时访问,那么这些代码块中的锁就可以被消除,因为不需要花费额外的时间来进行同步操作。
如:StringBuffer的关键方法都带有synchronized, 但如果只是在单线程中执行这写方法, 那么这些加锁操作是没有必要的,此时编译器就会把加锁操作去掉。
♩锁粗化
锁粗化是指将多个连续的细粒度锁(synchronized包含的代码块较少)合并为一个粗粒度锁(synchronized包含的代码块较多),以减少锁竞争带来的性能开销。
实际开发过程中,使用细粒度锁,是期望释放锁的时候其他线程能使用锁。但是实际上可能并没有其他线程来抢占这个锁。这种情况 JVM 就会自动把锁粗化,避免频繁申请释放锁。
举个例子:
张三要打电话向老师问三个问题,打一次电话问一个问题,连续打了三次电话。锁优化相当于张三打一次电话就把三个问题一起问了。

 

♫Callable接口

Callable  是一个  interface,与 Runnable 类似,也是用来描述一个任务,不同的是 Runnable 描述的任务没有返回值,而 Callable 描述的任务有返回值。如果需要一个线程单独计算出某结果,Callable是比较合适的。
如:创建线程计算 1 + 2 + 3 + ... + 1000, 不使用 Callable 版本:
①.创建一个类 Result, 包含一个 sum 表示最终结果, lock 表示线程同步使用的锁对象。
②.main 方法中先创建 Result 实例, 然后创建一个 Runable,在 Runnable 里计算1 + 2 + 3 + ... + 1000,将 Runnable 传入线程 thread。
③.主线程同时使用 wait 等待线程 thread  计算结束。  ( 注意: 如果执行到 wait 之前, 线程 thread  已经计算完了, 就不必等待了)。
④.当线程 thread  计算完毕后, 通过 notify 唤醒主线程, 主线程再打印结果。
public class Test {
    static class Result {
        public int num = 0;
        public Object lock = new Object();
    }

    public static void main(String[] args) throws InterruptedException {
        Result result = new Result();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                int num = 0;
                for (int i = 1; i <= 1000; i++) {
                    num += i;
                }
                synchronized (result.lock) {
                    result.num = num;
                    result.lock.notify();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        synchronized (result.lock) {
            while (result.num == 0) {
                result.lock.wait();
            }
            System.out.println(result.num);
        }
    }
}
可以看到, 上述代码需要一个辅助类 Result, 还需要使用一系列的加锁和 wait notify 操作, 代码复杂,容易出错。
创建线程计算 1 + 2 + 3 + ... + 1000, 使用 Callable 版本:
①.创建一个匿名内部类, 实现 Callable 接口, Callable 带有泛型参数, 泛型参数表示返回值的类型。
②.重写 Callable call 方法, 完成累加的过程, 直接通过返回值返回计算结果。
③.把 callable 实例使用 FutureTask 包装一下。
④.创建线程 , 线程的构造方法传入 FutureTask . 此时新线程就会执行 FutureTask 内部的 Callable 的 call 方法, 完成计算, 计算结果就放到了 FutureTask 对象中。
⑤.在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕, 并获取到 FutureTask 中的结果。
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        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 thread = new Thread(futureTask);
        thread.start();
        int result = futureTask.get();
        System.out.println(result);
    }
}
可以看到, 使用 Callable 和 FutureTask 之后, 代码简化了很多, 也不必手动写线程同步代码了。
注:
Callable 通常需要搭配 FutureTask 来使用,FutureTask 用来保存 Callable 的返回结果,因为 Callable 往往是在另一个线程中执行的,啥时候执行完并不确定,FutureTask 就可以负责这个等待结果出来的工作。

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

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

相关文章

MySQL---存储过程

存储过程的相关概念 是一组为了完成特定功能的sql语句的集合&#xff0c;类似于函数 写好了一个存储过程之后&#xff0c;我们可以像函数一样随时调用sql的集合。 复杂的&#xff0c;需要很多sql语句联合执行完成的任务 存储过程再执行上比sql语句的执行速度更快&#xff0c…

CS224W5.2——Relational and Iterative Classification

本节中&#xff0c;我们介绍用于节点分类的关系分类器和迭代分类。 从关系分类器开始&#xff0c;我们展示了如何基于邻居的标签迭代更新节点标签的概率。然后讨论迭代分类&#xff0c;通过根据邻居的标签及其特征预测节点标签来改进集体分类。 文章目录 1. 框架2. 关系分类3.…

基于SpringBoot的SSMP整合案例(开启日志与分页查询条件查询功能实现)

开启事务 导入Mybatis-Plus框架后&#xff0c;我们可以使用Mybatis-Plus自带的事务&#xff0c;只需要在配置文件中配置即可 使用配置方式开启日志&#xff0c;设置日志输出方式为标准输出mybatis-plus:global-config:db-config:table-prefix: tb_id-type: autoconfiguration:…

【黑客】最适合小白的学习顺序

一、黑客是什么 原是指热心于计算机技术&#xff0c;水平高超的电脑专家&#xff0c;尤其是程序设计人员。但后来&#xff0c;黑客一词已被用于泛指那些专门利用电脑网络搞破坏或者恶作剧的家伙。 二、学习黑客技术的原因 其实&#xff0c;网络信息空间安全已经成为海陆空之…

Python基础教程:类--继承和方法的重写

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 什么是继承 继承就是让类与类之间产生父子关系&#xff0c;子类可以拥有父类的静态属性和方法 继承就是可以获取到另一个类中的静态属性和普通方法&#xff08;并非所有成员&#xff09; 在python中&#xff0c;新建的类可…

【紫光同创国产FPGA教程】——【PGL22G第十一章】以太网传输实验例程

本原创教程由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注www.meyesemi.com) 适用于板卡型号&#xff1a; 紫光同创PGL22G开发平台&#xff08;盘古22K&#xff09; 一&#xff1a;盘古22K开发板&#xff08;紫光…

建筑能源管理(9)——公共建筑能源管理技术

现阶段&#xff0c;在我国经济高速发展的同时&#xff0c;也面临着资源有限、能源消费急剧增长、能源供给与需求之间的矛盾日益突出等问题。数据显示&#xff0c;现阶段我国单位GDP的能耗水平是发达国家的3倍左右&#xff0c;这正是能源总体利用率较低所造成的。建筑能耗作为我…

2023年初学者入门 CV 指南概述

计算机视觉&#xff0c;是一个迅速发展的领域&#xff0c;将让你大开眼界。它的核心是教计算机像我们人类一样看和理解视觉信息。这份全面指南&#xff0c;将为我们揭示计算机视觉的基本概念&#xff0c;探索流行的应用程序&#xff0c;并瞥见计算机视觉的未来趋势。 计算机视觉…

FM8317-USB TYPE-C PD 多协议控制器

产品描述&#xff1a; FM8317是一款集成了USB Type-C、USB Power Delivery&#xff08;PD3.0&#xff09;、PPS的多协议端口控制器&#xff0c;为AC-DC适配器、车载充电器等设备提供高性价比的USB Type-C 端口充电解决方案。 FM8317内置的Type-C协议可以支持Type-C设备插入自动…

SparkAi创作系统ChatGPT网站源码+详细搭建部署教程+AI绘画系统+支持GPT4.0+Midjourney绘画

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

Leetcode-145 二叉树的后序遍历

递归 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this…

字节和美团软件测试面试1000问(含文档)

一、Linux系统应用和环境配置 1、Linux系统的操作命令给我说10个&#xff0c;一般用什么工具远程连接Linux服务器&#xff1f; 2、Linux中的日志存储在哪里&#xff1f;怎么查看日志内容&#xff1f; 3、Linux中top和ps命令的区别&#xff1f; 4、Linux命令运行的结果如何写…

iOS 17.2更新:15Pro支持拍摄空间视频!

苹果又为开发者预览版用户推送了iOS 17.2 Beta2测试版的更新&#xff0c;已经注册Apple Beta版软件计划的用户只需打开设置--通用--软件更新即可在线OTA升级至最新的iOS 17.2测试版。 本次更新包大小为750M左右&#xff0c;内部版本号为&#xff08;21C5040g&#xff09;&#…

c语言,将奇数和偶数分类

题目&#xff1a;输入一个整数数组&#xff0c;实现一个函数&#xff0c;来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分&#xff0c;所有偶数位于数组的后半部分。 思路&#xff1a;像冒泡排序那样&#xff0c;相邻两个数比较&#xff0c;两个都是偶数则不…

(免费版?)CLion Nova 强势登陆 C 和 C++ 开发领域

系列文章目录 文章目录 系列文章目录前言一、CLion Nova二、目标三、优势和改进四、显著差异五、如何安装 CLion Nova六、分享您的反馈意见总结 阿纳斯塔西娅-卡扎科娃 2023 年 11 月 9 日 前言 今天&#xff0c;我们宣布推出免费的 CLion 早期预览版&#xff0c;它使用 ReSh…

【Linux】Linux 中关于文件和文件夹的常用命令

Linux 中关于文件和文件夹的常用命令 讲解 Linux 常用命令的文章已经非常多了&#xff0c;而且有的文章也说的非常清楚详细。我们可能不会记住所有的命令&#xff0c;但对于工作中常用的命令应该熟记于心&#xff0c;最好的方式就是多多实践。 我们可以直接或者通过虚机的方式…

24 _ 二叉树基础(下):有了如此高效的散列表,为什么还需要二叉树?

上一节我们学习了树、二叉树以及二叉树的遍历,今天我们再来学习一种特殊的二叉树,二叉查找树。二叉查找树最大的特点就是,支持动态数据集合的快速插入、删除、查找操作。 我们之前说过,散列表也是支持这些操作的,并且散列表的这些操作比二叉查找树更高效,时间复杂度是O(…

Leetcode-101 对称二叉树

递归&#xff1a;主要思想&#xff1a;对称二叉树是左子树的左孩子右子树的右孩子&#xff0c;左子树的右孩子右子树的左孩子&#xff0c;递归实现思路较为清晰 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* Tr…

同城跑腿服务预约小程序的作用如何

无论是互联网服务化加快还是前几年疫情冲击&#xff0c;在同城生活服务场景中出现了很多商机&#xff0c;如外卖跑腿、校园跑腿、代买代送等&#xff0c;无论公司还是个人都借势不断提升自己品牌的影响力&#xff0c;并且依赖朋友圈不断提升生意营收。 同城跑腿品牌不少&#…

跨域:利用iframe实现跨域DOM互访的四种方式

注&#xff1a;跨域的知识点详见&#xff1a;跨域相关知识点 目录 实验验证环境配置&#xff1a; 1、利用document.domain降域 方法1&#xff1a; 方法2&#xff1a; 2、利用location.hash 3、利用window.name 4、利用postMessage(最推荐) 使用postmessage实现跨域访问…