Java多线程进阶

news2024/11/19 16:34:34

目录

  • 1.常见锁策略
    • 1.1 乐观锁和悲观锁
    • 1.2 读写锁和普通互斥锁
    • 1.3 重量级锁和轻量级锁
    • 1.4 挂起等待锁和自旋锁
    • 1.5 公平锁和非公平锁
    • 1.6 重入锁和非重入锁
    • 1.7 synchronized锁的特点
  • 2.CAS
    • 2.1 CAS实现原子类
    • 2.2 实现自旋锁
    • 2.3 CAS的ABA问题
    • 2.4 解决ABA问题
  • 3.synchronized的锁优化机制
    • 3.1 锁膨胀/锁升级
    • 3.2 锁粗化
    • 3.3 锁消除
  • 4.JUC
    • 4.1 Callable
    • 4.2 ReentrantLock
    • 4.3 信号量
    • 4.4 CountDownLatch

1.常见锁策略

1.1 乐观锁和悲观锁

    乐观锁:预期锁冲突的概率很低.
    乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作)只有到数据提交的时候才通过一种机制来验证数据是否存在冲突.
    乐观锁做的工作更多少,付出的成本更低,更高效.


     悲观锁:预期锁冲突的概率很高.

    悲观锁是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的.

   悲观锁做的工作更多,付出的成本更多,更低效.

1.2 读写锁和普通互斥锁

   对于普通的互斥锁,只有两个操作加锁和解锁,只要两个线程针对同一个对象加锁,就会产生互斥.
   

  对于读写锁来说,分成了三个操作:

  1.加读锁:如果代码只是进行读操作,就加读锁

  2.加写锁:如果代码中进行了修改操作,就加写锁

  3.解锁

     针对读锁和读锁之间,是不存在互斥关系的.读锁和写锁之间,写锁和写锁之间,才需要互斥.
     多线程同时读同一个变量不会有线程安全问题,而且在很多场景中,都是读操作多,写操作少(数据库索引).

1.3 重量级锁和轻量级锁

    重量级锁和轻量级锁与上面的乐观锁和悲观锁相似,前者可以认为是处理锁冲突的结果.

    重量级锁就是做了更多的事情,开销更大,轻量级锁,做的事情更少,开销更小,也可以认为,通常情况下,悲观锁一般都是重量级锁.乐观锁一般都是轻量级锁,但也不是绝对的

    在使用的锁中,如果锁是基于内核的一些功能来实现的(比如调用了操作系统提供的 mutex 接口),此时一般认为这是重量级锁(操作系统的锁会在内核中做很多的事情,比如让线程阻塞等待…)

    如果锁是纯用户态实现的,此时一般认为这是轻量级锁(用户态的代码更可控也更高效)

1.4 挂起等待锁和自旋锁

    挂起等待锁,往往就是通过内核的一些机制来实现的,往往较重,是重量级锁的一种典型实现.

    自旋锁往往就是通过用户态代码来实现的,往往教轻轻量级锁的一种典型实现.

1.5 公平锁和非公平锁

    公平和非公平指的是遵守先来后到的原则.

    公平锁:多个线程在等待一把锁的时候,谁是先来的,谁就能先获取到这个锁(遵守先来后到).

    非公平锁:多个线程在等待一把锁的时候不遵守先来后到(每个等待的线程获取到锁的概率都是均等的)

1.6 重入锁和非重入锁

   可重入直观来讲,同一个线程针对同一个锁,连续加锁两次,如果出现了死锁,就是不可重入如果不会死锁,就是可重入的.在这里插入图片描述
    上图的代码:外层先加了一次锁,里层又对同一个对象再加一次锁

    外层锁:进入方法则开始加锁.这次能够加锁成功,当前锁是没有人占用的.
    里层锁:进入代码块,开始加锁,这次加锁不能成功,因为锁被外层占用着呢得等外层锁释放了之后,里层锁才能加锁成功,外层锁要执行完整个方法,才能释放.

    对于可重入锁来说,上述连续加锁操作,不会导致死锁.
    可重入锁内部,会记录当前的锁被哪个线程占用的,同时也会记录一个“加锁次数",线程a第一次加锁的时候,显然能够加锁成功,锁内部就记录了当前的占用者是a,同时加锁次为1.后续a再进行加锁,此时就不是真加锁,而是单纯的把计数给加锁次数为2.

    后续解锁的时候,先把计数进行-1,当锁的计数减到0的时候,就真的解锁.
    可重入锁的意义就是降低了程序员的负担.(使用成本,提高了开发效率)但是也带来了代价,程序中需要有更高的开销(维护锁属于哪个线程,并且加减计数.降低了运行效率)

1.7 synchronized锁的特点

在这里插入图片描述

2.CAS

    CAS全称compare and swap:比较和交换
    CAS要做的事情,拿着寄存器/某个内存中的值和另外一个内存的值进行比较,如果值相同了,就把另一个寄存器/内存的值,和当前的这个内存进行交换
在这里插入图片描述
在这里插入图片描述

2.1 CAS实现原子类

在这里插入图片描述

public class demo21 {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger num=new AtomicInteger();
        Thread t=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                //相当于num++
                num.getAndIncrement();
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                //相当于num++
                num.getAndIncrement();
            }
        });
        t.start();
        t2.start();
        t.join();
        t2.join();
        System.out.println(num.get());
    }
 }

在这里插入图片描述
    这个代码里面不存在线程安全问题,基于 CAS 实现的 ++ 操作这里面就可以保证既能够线程安全,又能够比 synchronized 高效,synchronized 会涉及到锁的竞争两个线程要相互等待,CAS 不涉及到线程阻塞等待

在这里插入图片描述
在这里插入图片描述

2.2 实现自旋锁

在这里插入图片描述
在这里插入图片描述

2.3 CAS的ABA问题

    ABA的场景出现时,预期值和旧值相同会让线程以为这个值没有被改变过,然而“值相同=没有被改变过”无法成立,即使第二个线程确确实实改过这个值,只不过又改回来了。“值曾经发生过改动”这个事件就无法被观测到。对于需要跟踪值的改变过程(比如记录值改变的次数)的场景来说ABA问题就是致命的

在这里插入图片描述

2.4 解决ABA问题

在这里插入图片描述
在这里插入图片描述

    此处就要求每次针对余额进行修改,都让版本号+1,每次修改之前要先对比版本看看旧值和当前值是否一致.

    当引入版本号之后,t2 再尝试进行这里的比较版本操作就发现版本的旧值和当前值并不匹配因此就放弃进行修改,如果直接拿变量本身进行判定,因为变量的值有加有减,就容易出现 ABA 的情况

    现在是拿版本号来进行判定,要求版本号只能增加,这个时候就不会有 ABA 问题了

3.synchronized的锁优化机制

3.1 锁膨胀/锁升级

    体现了synchronized的自适应能力

在这里插入图片描述
    偏向锁,并不是真的加锁,只是做了一个标记,带来的好处就是后续如果没人竞争的时候就避免了加锁解锁的开销
    偏向锁,和懒汉模式也有点像,思路都是一致的,只是在必要的时候进行操作

3.2 锁粗化

    此处的粗细指的是"锁的粒度",加锁代码涉及到的范围.加锁代码的范围越大,认为锁的粒度越粗范围越小,则认为粒度越细
    到底锁粒度是粗好还是细好? 各有各的好
    如果锁粒度比较细,多个线程之间的并发性就更高,如果锁粒度比较粗,加锁解锁的开销就更小,编译器就会有一个优化,就会自动判定说,如果某个地方的代码锁的粒度太细了,就会进行粗化.
    如果两次加锁之间的间隔较大(中间隔的代码多),一般不会进行这种优化.如果加锁之间间隔比较小(中间隔的代码少),就很可能触发这个优化

3.3 锁消除

    有些代码,不需要加锁,结果上锁了,编译器就会发现这个加锁好像没啥必要,就直接把锁给去掉了。
    有的时候加锁操作并不是很明显,稍不留神就做出了这种错误的决定,StringBuffer,Vector等等在标准库中进行了加锁操作,在单个线程中用到了上述的类,就是单线程进行了加锁解锁。

4.JUC

4.1 Callable

    java.util.concurrent是关于多线程相关操作的一个类
    Callable 是一个 interface 也是一种创建线程的方式,Runnable不太适合于让线程计算值,例如,创建一个线程,让这个线程计算 1 + 2 + 3 + … + 1000如果基于Runnable来实现,就会比较麻烦,Callable 就是要解决 Runnable繁琐的问题
在这里插入图片描述

public class demo22 {
    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 t=new Thread(futureTask);
        t.start();
        System.out.println(futureTask.get());
    }
}

在这里插入图片描述

4.2 ReentrantLock

    ReentrantLock和synchronized一样都是可重入锁
    基础用法:lock(),unlock(),把加锁和解锁两个操作分开了
    这种分开的做法不太好,很容易遗漏unlock (容易出现死锁)

  ReentrantLock Lock=new ReentrantLock();
        Lock.lock();
        try{
            //working
        }finally {
            Lock.unlock();  //最后都会执行到unlock
        }

  ReentrantLock和Synchronized的区别:

  1.synchronized 是一个关键字(背后的逻辑是JVM 内部实现的,C++),       ReentrantLock 是一个标准库中的类(背后的逻辑是 Java 代码写的)

  2. synchronized 不需要手动释放锁,出了代码块,锁自然释放,ReentrantLock 必须要手动释放锁,要谨防忘记释放.

  3.synchronized 如果竞争锁的时候失败,就会阻塞等待,但是 ReentrantLock 除了阻塞等待之外,还有trylock,失败了直接返回.

  4.synchronized是一个非公平锁,ReentrantLock 提供了非公平和公平锁两个版本,在构造方法中,通过参数来指定当前是公平锁还是非公平锁.

  5.基于 synchronized 衍生出来的等待机制,是 wait和notify,功能是相对有限,基于ReentrantLock 衍生出来的等待机制,是 Condition 类(条件变量),功能要更丰富一些

在这里插入图片描述

4.3 信号量

    Semaphore是一个更广义的锁,锁是信号量里一种特殊情况,叫做“二元信号量",可用资源就一个,计数器的取值,非0即1.

    停车场入口一般会有个牌子,上面写着“当前空闲 xx 个车位每次有个车开进去,车位数-1,每次有个车开出来,车位数+1,这个牌子就是信号量,描述了可用资源(车位)的个数

    每次申请一个可用资源,计数器就-1(称为 P 操作)
    每次释放一个可用资源,计数器就+1(称为 V 操作)
    当信号量的计数已经是 0了,再次进行 P 操作就会阻塞等待

    如果申请次数超过资源个数,会发生阻塞,直到有资源释放.

public class demo23 {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore= new Semaphore(5);  //申请了5个资源
        semaphore.acquire();
        System.out.println("申请资源成功");
        
        semaphore.acquire();
        System.out.println("申请资源成功");
        
        semaphore.acquire();
        System.out.println("申请资源成功");
        
        semaphore.acquire();
        System.out.println("申请资源成功");
        
        semaphore.acquire();
        System.out.println("申请资源成功");
        
        semaphore.release();
        System.out.println("释放资源成功");
        
        semaphore.acquire();
        System.out.println("申请资源成功");
    }
}

在这里插入图片描述

4.4 CountDownLatch

    CountDownLatch叫做倒计时锁存器,通俗解释是终点线.
    countDown给每个线程里面去调用,就表示到达终点了.
    await是给等待线程去调用,当所有的任务都到达终点了,await 就从阻塞中返回,表示任务完成

在这里插入图片描述

public class demo24 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch count=new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            Thread t =new Thread(()->{
                try {
                    Thread.sleep(3000);
                    System.out.println(Thread.currentThread().getName()+"到达终点");
                    count.countDown();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();
        }
        count.await();
        System.out.println("比赛结束");
    }
}

在这里插入图片描述

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

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

相关文章

“华为杯”研究生数学建模竞赛2004年-【华为杯】B题:有交货时间限制的大规模实用下料问题(附优秀论文)

赛题描述 “下料问题(cutting stock problem)”是把相同形状的一些原材料分割加工成若干个不同规格大小的零件的问题,此类问题在工程技术和工业生产中有着重要和广泛的应用. 这里的“实用下料问题”则是在某企业的实际条件限制下的单一材料的下料问题。 一个好的下料方案首先…

axios踩坑,不同版本默认Content-Type不同

文章目录1、请求封装2、问题axios 0.21源码axios 1.2源码总结1、请求封装 封装代码&#xff1a; /**axios封装* 请求拦截、相应拦截、错误统一处理*/ import axios from axios import QS from qs; import { Message } from element-ui// 环境的切换 if (process.env.NODE_ENV…

大数据必学Java基础(一百二十):Maven工程的介绍与创建

文章目录 Maven工程的介绍与创建 一、Maven工程类型 1、POM工程 2、JAR工程 3、WAR工程

Cookie、Session、Token、JWT详解

Cookie、Session、Token、JWT 什么是认证&#xff08;Authentication&#xff09; 通俗地讲就是验证当前用户的身份&#xff0c;证明“你是你自己”&#xff08;比如&#xff1a;你每天上下班打卡&#xff0c;都需要通过指纹打卡&#xff0c;当你的指纹和系统里录入的指纹相匹…

盖子的c++小课堂——第九讲:文件输入输出

前言 作者&#xff1a;哈咯大家好&#xff0c;我是盖…… 粉丝&#xff1a;更新更新&#xff01;再不更新我来偷你家马桶盖&#xff01;快更快更~~ 作者&#xff1a;子的盖…… 粉丝&#xff1a;再不更我真来偷你家马桶盖了昂~~ 作者&#xff1a;……啊&#xff01;&#…

实用!ArcGIS更改字段名称、类型、顺序的技巧

01需求说明 常规来说。ArcGIS属性表中的字段一旦建立&#xff0c;他的顺序、字段名称、字段的类型是没有办法更改的。 我们在属性表中拖动字段&#xff0c;也只是改变了字段的临时显示顺序。一旦数据重新加载就会显示会原来的顺序。 那么有什么技巧来解决这样的问题&#xff0c…

python爬虫的简单复习1【使用etree进行XPath解析】

一、相关知识1.1 etree的使用1.1.1 编码流程将HTML文本加载到etree对象中调用etree的xpath()函数完成标签定位对标签为所欲为&#xff08;此时获得的标签其实是xpath()函数返回的对象&#xff09;1.1.2 环境安装pip install lxml1.1.3 实例化etree对象# 首先导入模块 from lxml…

优思学院|如何更有效地进行5why分析(五问法)?

所谓5-why&#xff08;五个为什么或五问法&#xff09;&#xff0c;意思是要问五次 "为什么"&#xff0c;直到你找到问题的根本原因。具体的过程是&#xff1a;首先确定问题&#xff0c;然后回答"为什么"&#xff0c;并记录答案。第二次回答"为什么&q…

虚拟串口及其在串口转以太网中的应用

本文介绍虚拟串口的概念&#xff0c;以及如何在串口转以太网中利用该技术。 1.虚拟串口的概念 虚拟串口是用操作系统的虚拟驱动技术产生的串口&#xff08;COM口&#xff09;&#xff0c;相对于计算机本身的硬件串口&#xff08;COM1等&#xff09;来说虚拟串口并不对应一个物…

贪心法讲解

目录 一、前言 二、贪心法 1、优缺点 2、例子&#xff1a;最少硬币问题 3、贪心和动态规划 4、例题&#xff1a;快乐司机&#xff08;lanqiaoOJ题号1513&#xff09; 5、例题&#xff1a;旅行家的预算&#xff08;lanqiaoOJ题号775&#xff09; 三、例题 1、翻硬币&am…

数据结构:线性表的类型定义

线性表的类型定义 线性表是最简单、也是最基本的一种线性数据结构。它有两种存储表示方法&#xff1a;顺序表和链表&#xff0c;它的主要基本操作是插入、删除和查找。 线性表是n(n>0)个数据元素的有限序列&#xff0c;表中各个数据元素具有相同特性&#xff0c;即属同一数…

LeetCode 114. 二叉树展开为链表

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓LeetCode 114. 二叉树展开为链表&#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 一、题目名称 Leet…

2021年大数据挑战赛A题智能运维中的异常检测与趋势预测

2021年大数据挑战赛 A题 智能运维中的异常检测与趋势预测 原题再现&#xff1a; 异常检测&#xff08;异常诊断/发现&#xff09;、异常预测、趋势预测&#xff0c;是智能运维中首当其冲需要解决的问题。这类问题是通过业务、系统、产品直接关联的 KPI 业务指标进行分析诊断&…

MD语法 官方参考

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

Python小技巧:if __name__ == “__main__“ 的作用

前言 这里是Python小技巧的系列文章。这是第一篇&#xff0c;if __name__ "__main__" 的作用。 在编写Python程序时候&#xff0c;总是习惯性的在文件的末尾添加这么一段代码。 if __name__ "__main__":...至于它的作用是什么&#xff0c;先不管&#x…

内网渗透-src挖掘-外网打点到内网渗透-3层内网渗透测试记录(2)-2023年1月

1、确定目标之后&#xff0c;先进行信息搜集 对目标的先进行ip端口扫描&#xff0c;发现开放了22和80端口 2、访问80端口,为Laravel框架 3、发现该版本为Laravel v8.78.1 (PHP v7.4.3) 漏洞库中搜索&#xff0c;发现该版本的Laravel存在&#xff0c;CVE-2021-3129-Laravel De…

免费格式转换器有哪些?这几款一定要试试

相信在现在的工作中&#xff0c;大家都或多或少需要处理一些文件&#xff0c;有时候我们需要把PDF文件转为word文件&#xff0c;还可能需要将PDF文件转为PPT文件等等&#xff0c;这个时候我们就需要选择一款好用的格式转换器&#xff0c;如果转换文件量比较大的小伙伴们还需要重…

odoo16 修改odoo加载过程

在odoo加载过程中&#xff0c;title处会显示odoo以及odoo标签图片&#xff0c; 所以我们可以进行修改&#xff0c;将其公司化 1. 修改无公司名称时显示字样 <title t-esc"title or Odoo"/> <!--路径&#xff1a;addons/web/views/webclient_templates.xml…

2022.12 青少年软件编程(Python) 等级考试试卷(一级)

2022年12月 青少年软件编程&#xff08;Python&#xff09; 等级考试试卷&#xff08;一级&#xff09; 分数&#xff1a; 100 题数&#xff1a; 37 一、 单选题(共 25 题&#xff0c; 共 50 分) 1. 关于Python语言的注释&#xff0c;以下选项中描述错误的是&#xff1f;&#…

ThinkpadCPU锁频0.4G问题

1.问题描述 Thinkpad型号&#xff1a;E15 CPU&#xff1a;i5-10210U1.6GHz 系统&#xff1a;Win10家庭版 ThinkpadCPU锁频0.4G&#xff0c;具体是过程是今天我需要去楼下打印东西&#xff0c;要带着笔记本电脑&#xff0c;拔掉电源我就去楼下。 刚开始还是好好的&#xff0c;…