多线程问题(四)

news2024/11/14 22:03:56

目录

一、常见的锁策略

1、乐观锁 VS 悲观锁

2、读写锁 VS 普通的互斥锁

3、重量级锁 VS 轻量级锁 

4、挂起等待锁 VS 自旋锁 

6、公平锁 VS 非公平锁 

7、可重入锁 VS 不可重入锁

8、synchronized锁的性质 

二、CAS 

1、CAS的伪代码 

2、CAS的应用 

a、实现原子类 

b、实现自旋锁 

3、CAS中的ABA问题 

三、synchronized的工作原理

四、Callable接口用法 

五、ReentrantLock

六、Semaphore信号量 

七、CountDownLatch 


一、常见的锁策略

1、乐观锁 VS 悲观锁

乐观锁和悲观锁是处理锁冲突时的态度。

乐观锁:预测锁冲突发生的概率很低,做的工作就比较少,成本低,效率高。

悲观锁:预测锁冲突发生的概率很高,做的工作就比较多,成本高,效率低。

2、读写锁 VS 普通的互斥锁

普通的互斥锁:只有加锁和解锁两个操作。

读写锁:对于读操作就需要加读锁,对于写锁就需要加写锁,完了之后都必须解锁。

读锁和读锁之间不存在互斥关系,但是读锁和写锁、以及写锁和写锁之间都存在互斥。

读写锁适合不频繁写、频繁读的场景。

3、重量级锁 VS 轻量级锁 

重量级锁:做的事情比较多,开销多。

轻量级锁:做的事情比较少,开销小。

上述的悲观锁常认为是重量级锁,乐观锁被认为是轻量级锁。

通常要经过内核态来实现功能的锁被认为是重量级锁,只是在用户态实现功能的是轻量级锁。 

4、挂起等待锁 VS 自旋锁 

挂起等待锁:通过系统内核的相关机制来进行实现,通常是重量级锁。

自旋锁:通过用户态的代码来进行实现,往往是轻量级锁。

6、公平锁 VS 非公平锁 

此处公平的标准是:是否遵循先来后到。

公平锁:多个线程在等待锁的时候,遵守先来后到的原则,谁先来的,谁就先得到该锁。

非公平锁:多个线程在等待锁的时候,不遵守先来后到的原则。

注意:公平锁和非公平锁获取锁的概率是相等的。

7、可重入锁 VS 不可重入锁

可重入锁:对于一个线程连续加两把锁,不会产生死锁现象的锁。

不可重入锁:一个线程连续加两把锁,会产生死锁现象的锁。

8、synchronized锁的性质 

对于synchronized锁存在以下性质:

  • 既是乐观锁又是悲观锁,根据锁竞争的激烈程度进行自适应。
  • 是普通的互斥锁。
  • 也是根据锁竞争的激烈程度决定是重量级锁还是轻量级锁。
  •  挂起等待锁部分基于重量级锁来实现,自旋锁部分是基于轻量级锁部分来实现。
  • 是非公平锁。
  • 是可重入锁。

二、CAS 

CAS的全称是Compare And Swap,意思就是比较并交换,是一条计CPU指令。

假设内存中的原数据是A,预期的旧值为B,需要修改的值为C,那么一个CAS涉及如下的操作:

  1. 首先比较A和B是否相等
  2. 若相等,则将A与C进行交换,将C写入内存
  3. 最后,返回是否成功交换

1、CAS的伪代码 

boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}

当多线程同时对某个资源进行CAS操作,只有一个线程会操作成功,别的线程也不会发生阻塞 ,只是会返回操作失败。

CAS是一种乐观锁。

2、CAS的应用 

a、实现原子类 

在Java的标准库中,有java.util.concurrent.atomic包,其中对int、long等常见的数据类型进行了封装,并且保证了是线程安全的。

public static void main(String[] args) throws InterruptedException {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        Thread t1=new Thread(()->{
            for(int i=0;i<3000;i++){
                //此处为自增操作
                atomicInteger.getAndIncrement();
            }
        });
        Thread t2=new Thread(()->{
            for(int i=0;i<3000;i++){
                //此处为自增操作
                atomicInteger.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(atomicInteger.get());

    }
}

运行结果:

使用CAS实现原子类是线程安全的,虽然是多线程共享同一变量,因为当一个线程预期的旧值与内存的值不相等时就会修改失败,就会进行下一次的循环,也不会发生阻塞。

b、实现自旋锁 

        private Thread owner=null;
        public void lock(){
            while(!CAS(this.owner,null,Thread.currentThread())){

            }
        }
        
        public void unLock(){
            this.owner=null;
        }
        

在while循环中如果当前的owner值为null,就将owner值设为当前线程值,如果当前的owner值不为null,当前尝试加锁的线程就会一直重复的进行while循环,也就是自旋(忙等)。 

3、CAS中的ABA问题 

什么是ABA问题?

ABA问题就是在CAS进行比较的时候不能确定内存中的值是不是未发生任何改动的预期的旧值,就比如说有两个线程,线程1首先拿到内存中的值记录为oldValue,然后将oldValue

与内存中的值进行对比,但是此时有可能是线程2对内存中的值先加1再减1进行了两次操作,虽然此时内存中的值未变,但实际上已经进行了两次修改,此时线程1就无法判断内存中的值是oldValue还是已经经历了一个变化过程。

ABA问题的解决方案:

引入版本号,版本号只能向一个方向变化,例如在线程进行修改内存的值的时候就将版本号加1,使用CAS在将内存中的值进行读到寄存器的时候不但需要读取内存中的数值,还需要记录版本号,在比较的时候,既要对数值与期待的旧值进行比较,还需要比较版本号,如果都是一致的,才会对内存中的值进行交换,交换完对版本号+1。

这样的引入版本号的CAS是一种乐观锁。

三、synchronized的工作原理

synchronized的加锁过程

首先是未加锁,首个线程加锁就会进入偏向锁状态,也就是尝试加锁的一种状态,只是做了个标记,此处和懒汉模式比较相似,就是在必要的时候才进行加锁,如果有其他多线程也要进行加锁就会进入自旋锁状态,是比较轻量级的一种锁,进一步多线程锁竞争加剧就会进入到重量级锁的状态

synchronized进行锁粒度的优化:

一般情况下synchronized会进行锁粒度从细向粗进行优化,此处的粒度指的是加锁代码涉及的范围,设计的代码范围大,此处锁的粒度就比较粗,反之,锁的粒度就比较细。如果锁之间的代码间隔较小就会触发synchronized进行粗化。

例如:在一段for循环的代码中进行加锁,这样频繁进行加锁就会到导致锁竞争加剧,synchronized就会进行优化,将锁加到整个for循环的外面,使锁的粒度变粗,从而提高代码运行的效率。

 synchronized进行锁消除优化:

我们在写代码的时候有时候就会在不该加锁的地方进行上锁,造成资源浪费,这时候就会触发synchronized进行锁消除的优化,例如在使用StringBuffer、Vector等这样在标准库中已经进行了加锁操作的类在单线程环境下使用的话就会进行锁消除。

四、Callable接口用法 

Callable接口也是创建线程的一种方式,相比Runnable接口,Callable接口可以让线程具有返回值的,方便使用多线程计算结果。

例如使用Runnable接口创建线程来计算1+2+……+100 的运算结果。

首先需要创建一个类来接收求的sum值,然后创建一个线程t计算结果,利用锁起到join方法的作用,使main线程等到t线程计算完之后才结束。

class Result{
    public int sum;
    public Object locker=new Object();
}
public class CountSum {
    public static void main(String[] args) throws InterruptedException {
        Result result=new Result();
        Thread t=new Thread(()->{
            int sum=0;
            for(int i=1;i<=100;i++){
                sum+=i;
            }
            synchronized (result.locker){
                result.sum=sum;
                result.locker.notify();
            }
        });
        t.start();
        synchronized (result.locker){
            while(result.sum==0){
                result.locker.wait();
            }
        }
        System.out.println(result.sum);
    }

}

使用Callable接口创建线程计算1+2+3+……+100的结果。

public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable=new Callable() {
            @Override
            public Object call() throws Exception {
                int sum=0;
                for(int i=0;i<=100;i++){
                    sum+=i;
                }
                return sum;
            }
        };
        FutureTask<Integer> futureTask=new FutureTask<>(callable);
        Thread t=new Thread(futureTask);
        t.start();
        int sum=futureTask.get();
        System.out.println(sum);
    }

使用Callable接口创建线程时利用泛型可以指定类型, 需要创建匿名内部类并重写call方法,还需要使用FutureTask类对Callable接口进行封装,再创建线程时对FutureTask进行封装,启动线程后,使用futureTask类的get()方法得到返回结果。

五、ReentrantLock

ReentrantLock也是一种可重入锁,与Synchronized一样也是实现线程间的互斥效果。

但是ReentrantLock与Synchronized也存在以下区别:

  • synchronized出了代码段之后会自动释放锁,而ReentrantLock需要手动进行lock()加锁和unlock()解锁操作。
  • synchronized在多线程申请锁失败时会进入阻塞等待状态,但是ReentrantLock除了阻塞等待还可以使用tryLock()方法等待一段时间后就会直接返回失败。
  • synchronized是Java标准库中的,是在JVM外基于Java实现的,ReentrantLock是JVM内部通过C++实现。
  • synchronized是非公平锁,但是ReentrantLock默认是非公平锁,但是在实例化对象时可以通过设定参数为true来设置为公平锁。
  • synchronized通过wait和notify实现等待唤醒机制,而ReentrantLock搭配Condition类通过设置条件变量可以精确实现等待唤醒机制。

六、Semaphore信号量 

Semaphore信号量是一种广义上的锁,也是一个计数器,通过控制可用资源的数目来实现阻塞。

在Semaphore信号量中通过P操作(acquire方法)来申请资源,同时资源数就会减1;当资源数为0时继续进行P操作时就会阻塞。

通过V操作(release方法)释放资源,资源数加1。

可以对之前的生产者和消费者模型来使用信号量进行控制,默认信号量为0,生产者不断进行V操作释放资源,消费者通过P操作申请资源。

public static void main(String[] args) {
        Semaphore semaphore=new Semaphore(0);
        Thread producer=new Thread(()->{
            try {
                while(true){
                    semaphore.release();
                    System.out.println("生产者生产成功");
                    Thread.sleep(500);
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        producer.start();
        Thread consumer=new Thread(()->{
            try {
                while(true){
                    semaphore.acquire();
                    System.out.println("消费者消费成功");
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        consumer.start();
    }

七、CountDownLatch 

CountDownLatch可以同时等待多个线程执行结束。

使用CountDownLatch就好像在进行一场比赛,当所有选手都冲过终点比赛才会结束。

CountDownLatch也应用于多线程的场景,例如下载一个比较大的文件时通常会将文件分成几个部分,由几个线程来进行下载,全部都下载结束之后,文件下载完成。

例如:利用5个线程来实现任务: 

 public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"已完成");
                countDownLatch.countDown();
            }
        };
        for(int i=0;i<5;i++){
            new Thread(runnable).start();
        }
        countDownLatch.await();
        System.out.println("总任务完成");
    }

运行结果:

 

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

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

相关文章

【Spring系列】一篇文章开启你的 SpringBoot 之旅

SpringBoot 快速入门一. 环境搭建1.1 环境准备Java环境准备(本人是 JDK 1.8)开发工具 IntelliJ IDEAMaven (可使用 IntelliJ IDEA 自带)二. 快速开始2.1 新建项目打开IDEA 新建 SpringBoot 项目2.2 运行项目Spring Boot 项目运行分析尝试在浏览器中访问2.3 完成第一个接口新建c…

ArcGIS基础实验操作100例--实验30清除坐标系信息

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验30 清除坐标系信息 目录 一、实验背景 二、实验数据 三、实验步骤 方法一&#xff…

运维管理平台OEM定制集成开发,激发IT价值

对硬件设备商而言&#xff0c;借助优秀的网管、运维管理平台&#xff0c;可以形成完整的产品解决方案&#xff0c;直接提升产品的形象和适用范围。同时还可以通过网管、运维管理平台&#xff0c;切入到外围的产品及集成领域&#xff0c;并在用户后续的升级改造活动中占据有利位…

12.31日报

纠正前天的错误&#xff1a; 前天我写&#xff1a; 不知道在mapper中的增删改方法返回值int的值&#xff0c;和含义&#xff0c;在调用方法时也没有定义int来接参&#xff0c;都是直接调用。于是我定义int i&#xff0c;并打印输出&#xff0c;使用postman调用接口方法&#xf…

R语言hdnom包进行高维惩罚 Cox 回归模型绘制列线图-校准曲线-时间依赖ROC-外部验证

Hdnom包可以用于用于给高维数据构建Cox 模型、绘制列线图-校准曲线-时间依赖ROC-外部验证&#xff0c;而且Hdnom包简化了建模过程&#xff0c;带有自动选择变量功能&#xff0c;将用户从繁琐且容易出错的调参过程中解放出来. hdnom提供了多项自动调参和模型选择功能&#xff…

【OpenAI】基于 Gym-CarRacing 的自动驾驶项目 | 前置知识介绍 | 项目环境准备

猛戳&#xff01;跟哥们一起玩蛇啊 &#x1f449; 《一起玩蛇》&#x1f40d; &#x1f4ad; 写在前面&#xff1a; 本篇是关于多伦多大学自动驾驶专业项目 Gym-CarRacing 的博客。GYM-Box2D CarRacing 是一种在 OpenAI Gym 平台上开发和比较强化学习算法的模拟环境。它是流行…

jscharting.js v3.3.1.20220428 Crack

jscharting.js 3.3 版带来了主要的新组织结构图和甘特图扩展&#xff0c;包括甘特关键路径图、组织连接线样式和选择、仪表板小部件、象形图等等。图表渐变支持扩展到包括图标和动态点和系列渐变填充。 JSCharting 团队使用新功能、图表类型和图表小部件制作了超过 55 个高级示…

从发展的趋势来看,数字技术理应是产业互联网时代的驱动力

事实上&#xff0c;以往&#xff0c;我们所经历的那个互联网玩家频出的年代&#xff0c;其实就是一个以互联网技术为主导的年代。在那样一个年代里&#xff0c;互联网技术几乎是解决一切痛点和难题的万能解药&#xff0c;几乎是破解一切行业痛点和难题的杀手锏。任何一个行业&a…

【Java语言】—顺序结构、分支结构

流程控制语句 Java提供了一些流程控制语句&#xff0c;来控制程序的执行流程。 1.顺序结构 按照代码的先后顺序&#xff0c;以此执行程序。 2.分支结构 &#xff08;1&#xff09;if分支 根据判断的结果&#xff08;真或假&#xff09;决定执行某个分支的代码。 if分支有三…

Docker+Nginx打包部署前后端分离项目

DockerNginx打包部署前后端分离项目1、问题描述2、项目打包2.1 前端项目打包2.1.1 修改vue.config.js文件2.1.2 router配置中添加base属性2.1.3 打包前端项目2.2 后端项目打包2.3 将前端和后端的打包文件上传到服务器3 nginx反向代理配置4、后端通过Dockerfile打包成docker镜像…

目标检测-锚框

目标检测算法通常会在输入图像中采样大量的区域&#xff0c;然后判断这些区域中是否包含我们感兴趣的目标&#xff0c;并调整区域边界从而更准确地预测目标的真实边界框&#xff08;ground-truth bounding box&#xff09;。 不同的模型使用的区域采样方法可能不同。 这里我们介…

vue3 antd项目实战——Form表单的重置【使用resetFields()重置form表单数据、清空输入框】

vue3 ant design vue项目实战——Form表单【resetFields重置form表单数据】关于form表单的文章场景复现resetFields()重置表单数据项目实战关于form表单的文章 文章内容文章链接Form表单提交和校验https://blog.csdn.net/XSL_HR/article/details/128495087?spm1001.2014.3001…

十二、Kubernetes核心技术Service详解、实例

1、概述 我们都知道Kubernetes会为每个pod分配一个独立的IP&#xff0c;然而却存在如下问题&#xff1a; Deployment控制的3个pod&#xff0c;其中一个Pod出现问题&#xff0c;这个时候给销毁重新创建后Pod Ip会变化Pod IP 仅仅是集群内可见的虚拟IP&#xff0c;外部无法访问 …

AcWing的Linux基础课学习笔记(未学完)

目录版本B站试听课&#xff08;1.常用文件管理命令&#xff09;1.1文件系统1.2文件管理常用指令版本 服务器&#xff1a;Linux Ubuntu 20.04     我的笔记本&#xff1a;Acer Nitro AN515-55&#xff08;所以如果我要在AC Terminal里复制粘贴的话分别是&#xff1a;CtrlFnI…

autoload魔术方法的妙用

前言&#xff1a; __autoload魔术方法从PHP7.2.0开始被废弃&#xff0c;并且在PHP8.0.0以上的版本完全废除。取而代之的则是spl_autoload_register&#xff0c;但是本文还是研究__autoload。 什么是autoload魔术方法&#xff1f; 首先还是从官方手册中下手&#xff0c;了解a…

C++线程池的一种实现

线程池是实际开发中提高软件性能和稳定性的一种基本手段。可以想一下&#xff0c;如果程序中不用多线程&#xff0c;那执行效率会很低&#xff0c;如果运行线程太多&#xff0c;操作系统又吃不消&#xff0c;程序性能和稳定性会收到威胁。所以使用线程池技术诞生了&#xff0c;…

争做八桂好网民网络评选投票小程序投票的优劣微信怎么投票

用户在使用微信投票的时候&#xff0c;需要功能齐全&#xff0c;又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便&#xff0c;用户可以随时使用手机微信小程序获得线上投票服务&#xff0c;很多用户都很喜欢“活动星投票”这款软件。“活动星投票”小程序在使…

【Linux】Linux进程的理解

如果不改变自己&#xff0c;就别把跨年搞的和分水岭一样&#xff0c;记住你今年是什么吊样&#xff0c;明年就还会是什么吊样&#xff01;&#xff01;&#xff01; 文章目录一、冯诺依曼体系结构&#xff08;硬件&#xff09;二、操作系统&#xff08;软件&#xff09;1.操作…

AWVS安装与使用(最新版2022.12.27更新)

AWVS安装与使用1.AWVS1.1.AWVS介绍1.2.AWVS下载2.AWVS安装2.1.AWVS安装流程2.1.1.运行安装2.1.2.安装位置2.1.3.设置账号密码2.1.4.端口设置2.1.5.远程设置2.1.6.安装证书2.2.AWVSpj2.2.1.pj软件设置2.2.2.运行pj软件2.2.3.成功过程2.2.4.失败过程&#xff08;成功跳过&#xf…

基于Jeecg-boot开发的物流仓储系统,含数据库文件,涵盖模块:用户管理、车辆管理、计划管理、仓库管理、库存管理、财务管理、统计报表等

物流管理系统 完整代码下载地址&#xff1a;基于Jeecg-boot开发的物流仓储系统 基础开发环境&#xff1a;由于有小伙伴在运行项目时版本号不一致产生的各种问题&#xff0c;这里可以统一下版本号。 JDK: 1.8Maven: 3.5MySql: 5.7Redis: 3.2 Node Js: 10.0 Npm: 5.6.0Yarn: 1…