JAVAEE—Callable接口,ReentrantLock,synchronized的工作过程

news2024/12/24 20:52:22

文章目录

  • Callable接口的用法
    • Callable与FutureTask类
  • 加锁的工作过程
    • 什么是偏向锁呢?
      • 举个例子
    • 轻量级锁
    • 重量级锁
  • ReentrantLock
    • ReentrantLock 的用法:

Callable接口的用法

Callable 是一个 interface . 相当于把线程封装了一个 “返回值”. 方便程序猿借助多线程的方式计算结果.
我们之前写的代码重线程内部的lambda表达式中现实的run方法其内部的返回值是void因此当我们想要返回在方法内部实现返回一个数字的时候都很难做到。而Callbale就解决了这个方法
在这里插入图片描述

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    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<=100;i++){
                    sum+=i;
                }
                return sum;
            }
        };
        FutureTask<Integer>futureTask=new FutureTask<>(callable);
        Thread thread=new Thread(futureTask);
       
        thread.start();
        System.out.println(futureTask.get());
    }
}

Callable与FutureTask类

请看上面的callbale接口的运用。我们可以看到这个接口中我们实现了一个call方法他的返回值是一个Integer,这里call方法是我们的核心方法我们看一下源代码在这里插入图片描述
我们发现这个源代码中其实也就只用一个call方法这个方法的返回类型是一个模板也就是说他的返回类型我们可以进行指定,因此我们这里实例处的类型是什么这里的返回值就是什么。
那么我们如何创建线程吗?是直接用实例出的对象作为new Thread()括号中的参数吗?
很明显不是如此我们需要一个中间类那就是FutureTask那么我们来看一下这里面的源代码熟悉一下继承关系
在这里插入图片描述

首先我们可以看到这个类继承了一个RunnableFuture这个类,我们不知道这个类是什么但是我们可以继续看源代码。
在这里插入图片描述

我们发现这个类继承Runnable然后我们再去想一下Thread的构造方法,是可以接受Runnable类的因此我们知道此时也可以接受这个FutureTask类而这个FutureTask类里面的构造函数有一个方法
在这里插入图片描述
我们可以知道FutureTask类可以接受callable类并且其内部就有一个Callable类。

加锁的工作过程

加锁的工作过程就是下面的这个过程
在这里插入图片描述
在刚开始的时候是无锁的一个状态。当第一个尝试加锁的进程出现的时候会进入偏向锁的状态。那么什么是偏向锁呢?

什么是偏向锁呢?

偏向锁你可以理解为钓鱼,因为偏向锁其实并不是真的加锁,而是加了一个标记记录这个锁属于哪个线程,但是此时并没有加锁,那么当出现另一个线程也去申请这个锁的时候,那么第一个线程才会对其加锁。偏向锁本质上相当于 “延迟加锁” . 能不加锁就不加锁, 尽量来避免不必要的加锁开销. 但是该做的标记还是得做的, 否则无法区分何时需要真正加锁.

举个例子

相当于有一个帅哥叫做小帅,
他勾搭上了一个美女叫做小美
但是他很享受这个暧昧的过程因此不愿意和小美捅破那层窗户纸
此时当又有一个帅哥出现和他一起竞争小美
这时候小帅就慌了他会火速与小美表白这时候由于小美和小帅呆的时间长因此小美会优先同意小帅的表白。
这就是一个偏向锁。


这时候就会进入下一个状态那就是轻量级锁

轻量级锁

随着其他线程进入竞争, 偏向锁状态被消除, 进入轻量级锁状态(自适应的自旋锁).
此处的轻量级锁就是通过 CAS 来实现.

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

当锁竞争激烈起来之后将会进入重量级锁状态

重量级锁

如果竞争进一步激烈, 自旋不能快速获取到锁状态, 就会膨胀为重量级锁此处的重量级锁就是指用到内核提供的 mutex .

执行加锁操作, 先进入内核态.
在内核态判定当前锁是否已经被占用
如果该锁没有占用, 则加锁成功, 并切换回用户态.
如果该锁被占用, 则加锁失败. 此时线程进入锁的等待队列, 挂起. 等待被操作系统唤醒.
经历了一系列的沧海桑田, 这个锁被其他线程释放了, 操作系统也想起了这个挂起的线程, 于是唤醒
这个线程, 尝试重新获取锁.

ReentrantLock

这个的意思很明显就是可重入锁,可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全.

ReentrantLock 也是可重入锁. “Reentrant” 这个单词的原意就是 “可重入”

ReentrantLock 的用法:

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

从这里我们可以看出来ReentrantLock 其实是一个类,他不是一个关键字是java封装的一个类那么这里面主要的方法就是上面的这三个我们来看一下如何使用

import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;

public class test_ReentrantLock {
    public static void main(String[] args) {
        ReentrantLock reentrantLock=new ReentrantLock();
        int n=5;
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                reentrantLock.lock();
                int sum=0;
                while(sum<n){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    sum++;
                    System.out.println("我是线程1");
                }
                reentrantLock.unlock();
            }
        });
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                reentrantLock.lock();
                int sum=0;
                while(sum<n){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    sum++;
                    System.out.println("我是线程2");
                }
                reentrantLock.unlock();
            }
        });
        thread2.start();
        thread.start();
    }
}

在这里插入图片描述
我们发现这时候打印的结果不会是乱序的说明此时加锁的目的是达到了的,可是这里如果只有这种应用是不是太低级了感觉要不要它无所谓啊。但其实这里面它最不同的就是trylock方法这个方法是很大的不同的。那么我们把代码改一下

import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;

public class test_ReentrantLock {
    public static void main(String[] args) {
        ReentrantLock reentrantLock=new ReentrantLock();
        int n=5;
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                reentrantLock.tryLock();//第一处改动
                int sum=0;
                while(sum<n){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    sum++;
                    System.out.println("我是线程1");
                }
                reentrantLock.unlock();
            }
        });
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                reentrantLock.lock();
                int sum=0;
                while(sum<n){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    sum++;
                    System.out.println("我是线程2");
                }
                reentrantLock.unlock();
            }
        });
        thread2.start();
        try {//第二处改动
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        thread.start();
    }
}

变成了上面的代码之后我们来观察一下结果。
在这里插入图片描述
我们发现此时的代码变成了乱序的了就像没有加锁一样而事实上确实也没有加锁,因为tryLock其实就是尝试加锁我们的t2线程是先启动的因此是先获取锁的这时候t1线程想要获取锁就获取不到,那么这时候t1线程就说那获取不到我就不获取了直接往下执行了,这就是和synchronized很大的不同之处。
抛出异常的原因是因为我们的t1线程没有获取倒锁但是代码里却有释放锁的代码因此抛出了异常。

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

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

相关文章

YoloV8改进策略:Neck改进|GCNet(独家原创)|附结构图

摘要 本文使用GCNet注意力改进YoloV8,在YoloV8的Neck中加入GCNet实现涨点。改进方法简单易用&#xff0c;欢迎大家使用&#xff01; 论文:《GCNet: Non-local Networks Meet Squeeze-Excitation Networks and Beyond》 非局部网络&#xff08;NLNet&#xff09;通过为每个查…

【教程】Kotlin语言学习笔记(六)——泛型

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【Kotlin语言学习】系列文章 第一章 《认识Kotlin》 第二章 《数据类型》 第三章 《数据容器》 第四章 《方法》 第五章 《L…

如何远程电脑连接?

远程电脑连接是指通过网络将计算机与远程设备连接起来&#xff0c;实现远程管理和操作的技术。在现代信息化社会中&#xff0c;远程电脑连接成为了人们工作和生活中的重要方面。远程电脑连接可以极大地提高工作效率和便利性&#xff0c;让我们能够在不同地点的计算机之间进行协…

【Servlet】服务器内部转发以及客户端重定向

文章目录 一、服务器内部转发&#xff1a;request.getRequestDispatcher("...").forward(request, response);二、客户端重定向&#xff1a;response.sendRedirect("");三、服务器内部转发代码示例四、客户端重定向代码示例 一、服务器内部转发&#xff1a…

【Vue】vue3简介与环境配置

文章目录 项目编码规范什么是 Vue&#xff1f;安装node环境nvm针对node版本惊醒管理的工具 项目编码规范 组合式API Typescript setup(语法糖) 什么是 Vue&#xff1f; Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;…

史上最强 PyTorch 2.2 GPU 版最新安装教程

一 深度学习主机 1.1 配置 先附上电脑配置图&#xff0c;如下&#xff1a; 利用公司的办公电脑对配置进行升级改造完成。除了显卡和电源&#xff0c;其他硬件都是公司电脑原装。 1.2 显卡 有钱直接上 RTX4090&#xff0c;也不能复用公司的电脑&#xff0c;其他配置跟不上。…

路由和远程访问是什么?

路由和远程访问在现代互联网时代中&#xff0c;扮演着至关重要的角色。它们为我们提供了便捷的信息传递途径&#xff0c;让不同地区的电脑、设备以及人们之间能够轻松进行通信和交流。 对于路由来说&#xff0c;它是连接互联网上的各个网络的核心设备。一台路由器可以将来自不同…

Linux——线程控制

目录 前言 一、线程创建 1.创建线程 2.线程传递结构体 3.创建多线程 4.收到信号的线程 二、线程终止 三、线程等待 四、线程分离 五、取消线程 六、线程库管理的原理 七、站在语言角度理解pthread库 八、线程的局部存储 前言 前面我们学习了线程概念和线程创建&…

揭开HTTP状态码的神秘面纱:基本概念全解析

在客户端与服务器之间的信息传输过程中&#xff0c;我们可以将其比喻为客户与快递员之间的包裹传递。那么服务器是如何通知客户端&#xff0c;操作是成功还是失败&#xff1f;或者有其他的一些情况呢&#xff1f;&#xff08;就像客户可以查询快递的状态&#xff09; 而这背后…

搜维尔科技:SenseGlove Nova 允许以最简单的方式操作机器人并与物体交互

扩展 Robotics 和 QuarkXR 人机界面 XR 应用 Extend Robotics 利用扩展现实技术&#xff0c;让没有机器人专业知识的个人能够远程控制机器人。他们的 AMAS 解决方案使操作员能够不受地理限制地轻松控制机器人。 需要解决的挑战【搜维尔科技】 目前&#xff0c;操作机器人是一…

10秒钟用python接入讯飞星火API(保姆级)

正文&#xff1a; 科大讯飞是中国领先的人工智能公众公司&#xff0c;其讯飞星火API为开发者提供了丰富的接口和服务&#xff0c;以支持各种语音和语言技术的应用。 步骤一&#xff1a;注册账号并创建应用 首先&#xff0c;您需要访问科大讯飞开放平台官网&#xff0c;注册一个…

最优算法100例之21-数组的逆序对

专栏主页:计算机专业基础知识总结(适用于期末复习考研刷题求职面试)系列文章https://blog.csdn.net/seeker1994/category_12585732.html 题目描述 逆序数: 在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一…

MQ消息队列详解以及MQ重复消费问题

MQ消息队列详解以及MQ重复消费问题 1、解耦2、异步调用3、流量削峰4、MQ重复消费问题&#xff0c;以及怎么解决&#xff1f;4.1、重复消费产生4.2、解决方法&#xff1a; https://blog.csdn.net/qq_44240587/article/details/104630567 核心的就是&#xff1a;解耦、异步、削锋…

redis链表结构和简单动态字符串(SDS)

1.双向链表 redis中的普通链表是双向链表。通过链表节点结构体可知有全驱节点和后继节点。 1.链表节点和链表 //adlist.h typedef struct listNode {struct listNode *prev; //前驱节点struct listNode *next; //后继节点void *value; //节点值 } list…

element-ui result 组件源码分享

今日简单分享 result 组件的源码实现&#xff0c;主要从以下三个方面&#xff1a; 1、result 组件页面结构 2、result 组件属性 3、result 组件 slot 一、result 组件页面结构 二、result 组件属性 2.1 title 属性&#xff0c;标题&#xff0c;类型 string&#xff0c;无默…

深入OceanBase内部机制:多租户架构下的资源隔离实现精讲

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 一、什么是OceanBase的多租户二、兼容模式2.1 MySQL 模式2.2 Oracle 模式 三、租户介绍3.1 系统租户3.2 用户租户3.3 Meta 租…

STM32应用开发——使用PWM+DMA驱动WS2812

STM32应用开发——使用PWMDMA驱动WS2812 目录 STM32应用开发——使用PWMDMA驱动WS2812前言1 硬件介绍1.1 WS2812介绍1.1.1 芯片简介1.1.2 引脚描述1.1.3 工作原理1.1.4 时序1.1.5 传输协议 1.2 电路设计 2 软件编程2.1 软件原理2.2 测试代码2.2.1 底层驱动2.2.2 灯效应用 2.3 运…

联想 Y9000P 连接网线速度慢 的 问题解决

参考帖子&#xff1a;求助&#xff0c;拯救者Y9000P 2022 i73060版本 有线网非常慢 无线网正常【笔记本吧】_百度贴吧 问题原因&#xff1a; 网卡驱动版本不对。不能用Win11版&#xff0c;要用Win10版。 问题解决&#xff1a; 1、卸载原驱动 2、下载Win10 驱动 并安装 下载…

探寻大数据思想的主要贡献者与核心内容

引言&#xff1a; 在当今数字化时代&#xff0c;大数据已成为企业和科学研究的关键要素。其背后的思想和概念不仅引领了数据处理和分析的革新&#xff0c;也推动了人类对于信息时代的理解与认知。 大数据思想的起源&#xff1a; 在信息爆炸的时代背景下&#xff0c;大数据思…

QT5-qmediaplayer播放视频及进度条控制实例

qmediaplayer是QT5的播放视频的一个模块。它在很多时候还是要基于第三方的解码器。这里以Ubuntu系统为例&#xff0c;记录其用法及进度条qslider的控制。 首先&#xff0c;制作一个简单的界面文件mainwindow.ui&#xff1a; 然后&#xff0c;下载一个mp4或其他格式视频&#x…