Java EE 进阶---多线程(一)

news2024/11/15 19:51:28

目录

一、常见的锁策略

乐观锁 vs 悲观锁

        重量级锁 vs 轻量级锁

读写锁&普通互斥锁

 自旋锁&挂起等待锁

可重入锁&不可重入锁

公平锁&非公平锁

synchronized实现了哪些锁策略?

二、Compare And Swap  比较并交换

 基于CAS的应用

 CAS实现自旋锁

 CAS 的 ABA 问题

解决ABA问题

三、Synchronized 原理

四、其他的优化操作

锁消除

 锁粗化

五、JUC


一、常见的锁策略

乐观锁 vs 悲观锁

悲观锁 :
总是假设最坏的情况,对运行环境持悲观态,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
乐观锁:
假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。
对运行环境持乐观态,刚开始不加锁,当有竞争的时候再去加锁。

重量级锁 vs 轻量级锁

在实现锁的过程中,消耗的资源多不多

轻量级锁:可以纯用户态的锁,消耗的资源比较小

重量级锁:可能会调用到系统的内核态,消耗的资源比较多

读写锁&普通互斥锁

在现实中并不是所有的锁都要互斥,互斥必然会消耗很多的资源,所以优化出读写锁
读锁:共享锁,读与读可以同时拿到锁资源
写锁:排他锁,不能与 写写,写读,读写

普通互斥锁:synchronized,只能一个线程拿到锁资源,其他的要参与锁竞争,没有竞争到锁的时候就要阻塞等待

 自旋锁&挂起等待锁

自旋锁:不停的询问资源是否被释放,如果释放了第一时间可以获取锁资源
挂起等待锁:等待通知之后再去竞争锁,并不会第一时问获取到锁资源


可重入锁&不可重入锁

可重入锁:对于同一个锁对象可以加多次锁
不可重入锁:不能对同一个锁对象加多次锁

公平锁&非公平锁

公平锁:先排队等待的线程先获取到锁资源
非公平锁:没有先来后到这么一说,谁抢到是谁的
所有有争抢的事情,绝大多数都是不公平的


synchronized实现了哪些锁策略?

  • 既是乐观锁与是悲观锁
  • 既是轻量级锁与重量级锁  轻量级锁是基于自旋锁实现的,重量级锁是基于挂起等待锁实现的
  • 是普通互斥锁
  •  既是自旋锁与是挂起等待锁
  •  是可重入锁
  • 是非公平锁


自旋锁是基于CAS实现的 

二、Compare And Swap  比较并交换

 

基于CAS的应用

 

 CAS实现自旋锁

自旋锁伪代码:

public class SpinLock {
    private Thread owner = null;
    public void lock(){
        // 通过 CAS 看当前锁是否被某个线程持有. 
        // 如果这个锁已经被别的线程持有, 那么就自旋等待. 
        // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
        while(!CAS(this.owner, null, Thread.currentThread())){
       }
   }
    public void unlock (){
        this.owner = null;
   }
}

 CAS ABA 问题

ABA 问题:

假设存在两个线程 t1 t2. 有一个共享变量 num, 初始值为 A.
接下来 , 线程 t1 想使用 CAS num 值改成 Z, 那么就需要先读取 num 的值 , 记录到 oldNum 变量中 .
使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z.
但是 , t1 执行这两个操作之间 , t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A
线程 t1 CAS 是期望 num 不变就修改 . 但是 num 的值已经被 t2 给改了 . 只不过又改成 A . 这个时候 t1 究竟是否要更新 num 的值为 Z ?
到这一步 , t1 线程无法区分当前这个变量始终是 A, 还是经历了一个变化过程 .
这就好比 , 我们买一个手机 , 无法判定这个手机是刚出厂的新手机 , 还是别人用旧了 , 又翻新过的手
.

解决ABA问题

给要修改的值, 引入版本号.版本号只增不减,  CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.

  • CAS 操作在读取旧值的同时, 也要读取版本号. 真正修改的时候, 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.
  • 如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了).
这就好比 , 判定这个手机是否是翻新机 , 那么就需要收集每个手机的数据 , 第一次挂在电商网站上的手机记为版本1, 以后每次这个手机出现在电商网站上 , 就把版本号进行递增 . 这样如果买家不在意这是翻新机, 就买 . 如果买家在意 , 就可以直接略过 .

 

三、Synchronized 原理

结合上面的锁策略 , 我们就可以总结出 , Synchronized 具有以下特性 ( 只考虑 JDK 1.8):
1. 开始时是乐观锁 , 如果锁冲突频繁 , 就转换为悲观锁 .
2. 开始是轻量级锁实现 , 如果锁被持有的时间较长 , 就转换成重量级锁 .
3. 实现轻量级锁的时候大概率用到的自旋锁策略
4. 是一种不公平锁
5. 是一种可重入锁
6. 不是读写锁

 

1) 偏向锁
第一个尝试加锁的线程 , 优先进入偏向锁状态 .
偏向锁不是真的 " 加锁 ", 只是给对象头中做一个 " 偏向锁的标记 ", 记录这个锁属于哪个线程 .
如果后续没有其他线程来竞争该锁 , 那么就不用进行其他同步操作了 ( 避免了加锁解锁的开销 )
如果后续有其他线程来竞争该锁 ( 刚才已经在锁对象中记录了当前锁属于哪个线程了 , 很容易识别
当前申请锁的线程是不是之前记录的线程 ), 那就取消原来的偏向锁状态 , 进入一般的轻量级锁状态 .
偏向锁本质上相当于 " 延迟加锁 " . 能不加锁就不加锁 , 尽量来避免不必要的加锁开销 .
但是该做的标记还是得做的 , 否则无法区分何时需要真正加锁 .
举个栗子理解偏向锁
假设男主是一个锁 , 女主是一个线程 . 如果只有这一个线程来使用这个锁 , 那么男主女主即使不领证结婚( 避免了高成本操作 ), 也可以一直幸福的生活下去 .
但是女配出现了 , 也尝试竞争男主 , 此时不管领证结婚这个操作成本多高 , 女主也势必要把这个动作完成了, 让女配死心 .
2) 轻量级锁
随着其他线程进入竞争 , 偏向锁状态被消除 , 进入轻量级锁状态 ( 自适应的自旋锁 ).
此处的轻量级锁就是通过 CAS 来实现 .
通过 CAS 检查并更新一块内存 ( 比如 null => 该线程引用 )
如果更新成功 , 则认为加锁成功
如果更新失败 , 则认为锁被占用 , 继续自旋式的等待 ( 并不放弃 CPU).
自旋操作是一直让 CPU 空转 , 比较浪费 CPU 资源 .
因此此处的自旋不会一直持续进行 , 而是达到一定的时间 / 重试次数 , 就不再自旋了 .
也就是所谓的 " 自适应 "
3) 重量级锁
如果竞争进一步激烈 , 自旋不能快速获取到锁状态 , 就会膨胀为重量级锁
此处的重量级锁就是指用到内核提供的 mutex .
执行加锁操作 , 先进入内核态 .
在内核态判定当前锁是否已经被占用
如果该锁没有占用 , 则加锁成功 , 并切换回用户态 .
如果该锁被占用 , 则加锁失败 . 此时线程进入锁的等待队列 , 挂起 . 等待被操作系统唤醒 .
经历了一系列的沧海桑田 , 这个锁被其他线程释放了 , 操作系统也想起了这个挂起的线程 , 于是唤醒
这个线程 , 尝试重新获取锁 .

四、其他的优化操作

锁消除

编译器+JVM 判断锁是否可消除. 如果可以, 就直接消除.

什么是 " 锁消除 "
有些应用程序的代码中 , 用到了 synchronized, 但其实没有在多线程环境下 . ( 例如 StringBuffer) 此时每个 append 的调用都会涉及加锁和解锁 . 但如果只是在单线程中执行这个代码 , 那么这些加锁解锁操作是没有必要的, 白白浪费了一些资源开销 .

 锁粗化

一段逻辑中如果出现多次加锁解锁 , 编译器 + JVM 会自动进行锁的粗化 .
锁的粒度 : 粗和细
StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");
实际开发过程中 , 使用细粒度锁 , 是期望释放锁的时候其他线程能使用锁 .
但是实际上可能并没有其他线程来抢占这个锁 . 这种情况 JVM 就会自动把锁粗化 , 避免频繁申请释
放锁 .

 


五、JUC

java.util.concurrent 包的简称,JDK1.5之后对多线程的一种实现,这个包下放的类都和多线程有关,提供了很多工具类

Callable 接口

Callable 是一个 interface . 相当于把线程封装了一个 " 返回值 ". 方便程序猿借助多线程的方式计算结果 .也是描述任务的接口.

 

public class Demo03_Callable {
    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 = 0; i < 5; i++) {
                    sum += i;
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println("等待1秒");
                }
                // 返回结果
                return sum;
            }
        };

        // 通过FutureTask类来创建一个对象,这个对象持有callable
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // 创建线程并指定任务
        Thread thread = new Thread(futureTask);
        // 让线程执行定义好的任务
        thread.start();
        // 获取线程执行的结果
        System.out.println("等待结果...");
        Integer result = futureTask.get();
        // 打印结果
        System.out.println(result);

    }
}

ReentrantLock

可重入互斥锁. synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全.

ReentrantLock synchronized 的区别:
  • synchronized 是一个关键字, JVM 内部实现的(大概率是基于 C++ 实现). ReentrantLock 是标准
       库的一个类 , JVM 外实现的 ( 基于 Java 实现 ).
  • synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活但是也容易遗漏 unlock.
  • synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃.
  • synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启公平锁模式.

 

public class Demo05_ReentrantLock {
    /**
     * ReentrantLock可以根据不同的Condition去休眠或唤醒线程
     * 同一把锁可以分为不同的休眠或唤醒条件
     */
    private static ReentrantLock reentrantLock = new ReentrantLock();
    // 定义不同的条件
    private static Condition boyCondition = reentrantLock.newCondition();
    private static Condition girlCondition = reentrantLock.newCondition();

    public static void demo05_Condition () throws InterruptedException {
        Thread threadBoy = new Thread(() -> {
            // 让处理男生任务的线程去休眠
            try {
                boyCondition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 唤醒处理女生任务的线程
            girlCondition.signalAll();
        });

        Thread threadGirl = new Thread(() -> {
            // 让处理女生任务的线程去休眠
            try {
                girlCondition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 唤醒处理男生任务的线程
            boyCondition.signalAll();
        });



    }

    /**
     * 创建读写锁
     */
    public static void demo04_ReadWriteLock () {
        // 创建
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        // 获取读锁, 共享锁,读与读可以同时进行
        readWriteLock.readLock();
        // 获取写锁,排他锁(互斥锁),读写,写读,写写不能共存
        readWriteLock.writeLock();
    }

    /**
     * 演示创建一个公平锁
     */
    public static void demo03_fair () {
        // 通过构造方法,传入true时为公平锁,false为非公平锁,默认为false
        ReentrantLock reentrantLock = new ReentrantLock(true);
    }

    /**
     * 模拟业务中如果出现异常情况,如何释放锁
     */
    public static void demo02 () throws Exception {
        // 创建一个ReentrantLock对象
        ReentrantLock reentrantLock = new ReentrantLock();
        // 加锁
        reentrantLock.lock();
        try {
            // TODO : 业务逻辑
            throw new Exception("业务出现异常");
        } finally {
            // 保证出现异常的时候也可以释放锁
            reentrantLock.unlock();
        }
    }

    /**
     * 演示基本方法
     * @throws InterruptedException
     */
    public static void demo01_lock() throws InterruptedException {
        // 创建一个ReentrantLock对象
        ReentrantLock reentrantLock = new ReentrantLock();
        // 加锁
        reentrantLock.lock();
        // 尝试加锁, 死等
        reentrantLock.tryLock();
        // 尝试加锁,有超时时间
        reentrantLock.tryLock(1, TimeUnit.SECONDS);

        // 释放锁
        reentrantLock.unlock();
    }
}

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

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

相关文章

File类、IO数据流介绍

文章目录 &#x1f412;个人主页&#x1f3c5;JavaSE系列专栏&#x1f4d6;前言&#xff1a;&#x1f380;File类的设计&#x1fa85;数据流的流向 &#x1f3c5;对数据操作的类&#x1f9f8;按单位划分&#x1f9f8;按封装类型划分 &#x1f380;整理File常用方法 &#x1f41…

解析HTTPS的加密

文章目录 简介加密原理对称加密常用的对称加密算法&#xff1a;简单解析DES 非对称加密常用的非对称加密算法&#xff1a;简单解析RSA算法公钥和私钥的制作&#xff1a;加密和解密过程&#xff1a; 加密过程数字证书摘要算法解析一下MD5算法 简介 HTTPS 使用了 SSL (Secure So…

2023最新软件测试八股文,能不能拿心仪Offer就看你背得怎样了

很多同学会问测试面试八股文有必要背吗&#xff1f; 我的回答是&#xff1a;很有必要。你可以讨厌这种模式&#xff0c;但你一定要去背&#xff0c;因为不背你就进不了大厂。 个人感觉目前各个大中厂面试不好的地方是&#xff0c;都在朝着背面试八股文的方向发展&#xff08;曾…

Android Studio实现文件管理器

项目目录 一、项目概述二、开发环境三、详细设计1、布局设计2、程序运行时申请权限3、查看文件4、删除文件5、搜索文件6、新建文件 四、运行演示 一、项目概述 本次带来的文件管理器&#xff0c;能够对SD卡的目录进行管理。主要功能包括新建文件&#xff08;夹&#xff09;、查…

【shell】shell编程之函数

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、概述二、函数的查看和删除1.查看 declare2.删除 declare 三、函数的返回值1.return 返回值2.echo 返回值 四、函数的参数传入与变量范围五、函数的应用1.阶乘2.…

分库分表如何处理主键ID

当关系型数据库数据量过大时&#xff0c;通常会采用分库分表降低数据库查表压力。分库分表有多种&#xff0c;有分一个库多张分表额&#xff0c;有分多个库多张表的。一般分库分表使用ShardingSphere分表&#xff0c;建分片键等。但是分库分表之后&#xff0c;主键ID如何处理呢…

Goby 漏洞更新 |Telesquare TLR-2005Ksh 路由器 getUsernamePassword 信息泄露漏洞

漏洞名称&#xff1a;Telesquare TLR-2005Ksh 路由器 getUsernamePassword 信息泄露漏洞 English Name&#xff1a;Telesquare TLR-2005Ksh getUsernamePassword Information Disclosure CVSS core: 9.0 影响资产数&#xff1a;25826 漏洞描述&#xff1a; Telesquare Tlr…

MATLAB--控制语句--数组操作--符号运算--绘图--文件和数据的处理

控制语句 MATLAB 常用的控制语句有for, while, if, switch 等。 一、循环语句 通过循环控制语句&#xff0c;可以重复执行代码块。循环有两类&#xff1a;for 循环和while 循环。 for 循环是一组语句重复固定的&#xff0c;指定的次数&#xff0c;while 循环是满足条件执行。…

【鸿蒙应用ArkTS开发系列】- http网络库使用讲解和封装

目录 前言http网络库组件介绍http网络库封装创建Har Module创建RequestOption 配置类创建HttpCore核心类创建HttpManager核心类对外组件导出添加网络权限 http网络库依赖和使用依赖http网络库&#xff08;httpLibrary&#xff09;使用http网络库&#xff08;httpLibrary&#x…

“虐人的”双亲委派机制

这些问题&#xff0c;看看你能回答上来多少个&#xff1a; 1、什么是双亲委派&#xff1f; 2、为什么需要双亲委派&#xff0c;不委派有什么问题&#xff1f; 3、”父加载器”和”子加载器”之间的关系是继承的吗&#xff1f; 4、双亲委派是怎么实现的&#xff1f; 5、我能不能…

国外空间服务器是否有利于SEO优化?

​  购买国外空间服务器&#xff0c;这是许多人在做网站时会考虑到的选择。在国内&#xff0c;由于网络环境和限制&#xff0c;所以选择国外的空间似乎是一个不错的选择。但是&#xff0c;国外空间服务器是否对SEO优化有影响?这是一个值得讨论的问题。 1.服务器响应速度 如果…

跟着LearnOpenGL学习4--着色器

文章目录 一、前言二、GLSL2.1、着色器结构2.2、数据类型2.2.1、向量 2.3、输入与输出2.4、uniform2.5、更多属性 三、着色器类 一、前言 在之前绘制三角形的博文中&#xff0c;我们已经接触到了着色器&#xff0c;但是肯定有许多疑问&#xff0c;本文来详细了解一下着色器&am…

【17】SCI易中期刊推荐——计算机信息系统电子与电气(中科院4区)

💖💖>>>加勒比海带,QQ2479200884<<<💖💖 🍀🍀>>>【YOLO魔法搭配&论文投稿咨询】<<<🍀🍀 ✨✨>>>学习交流 | 温澜潮生 | 合作共赢 | 共同进步<<<✨✨ 📚📚>>>人工智能 | 计算机视觉…

Flink从入门到精通之-09状态编程

Flink从入门到精通之-09状态编程 Flink 处理机制的核心&#xff0c;就是“有状态的流式计算”。我们在之前的章节中也已经多次提到了“状态”&#xff08;state&#xff09;&#xff0c;不论是简单聚合、窗口聚合&#xff0c;还是处理函数的应用&#xff0c;都会有状态的身影出…

Spring Cloud第二季--Spring Cloud Bus

文章目录 Spring Clud Bus什么是总线基本原理 牛刀小试 Spring Clud Bus 在Spring Cloud学习–配置中心&#xff08;Config&#xff09;中实现了集中管理微服务配置、不同环境不同配置、运行期间也可动态调整、配置修改后可以自动更新的需求&#xff0c;但同时也有一个弊端&am…

服务器电源线:同为科技(TOWE)机房工程专用电源延长线

工程机房电源延长线 众所周知&#xff0c;世界上不同国家或地区所使用的插头、插座标准有所不同&#xff0c;在庞大复杂的数据中心计算集群内&#xff0c;需要电源供电才能正常工作&#xff0c;因此&#xff0c;服务器电源线是连接电源分配器和服务器的基本配件。在机房服务器…

如何通过优化服务器提升网站的SEO排名

在当今数字化时代&#xff0c;拥有一个高效、稳定的服务器对于网站的成功至关重要。然而&#xff0c;服务器不仅仅是为了提供网站的基本运行&#xff0c;它还可以对搜索引擎优化(SEO)起到关键作用&#xff0c;于是就有了多IP站群服务器这样对SEO非常友好的服务器。通过优化服务…

Goby 漏洞更新 |Telesquare TLR-2005Ksh 路由器 setSyncTimeHost 命令执行漏洞

漏洞名称&#xff1a;Telesquare TLR-2005Ksh 路由器 setSyncTimeHost 命令执行漏洞 English Name&#xff1a;Telesquare TLR-2005Ksh setSyncTimeHost RCE CVSS core: 9.8 影响资产数&#xff1a;25826 漏洞描述&#xff1a; Telesquare Tlr-2005Ksh是韩国Telesquare公司…

5月12日作业

作业1&#xff1a; 作业2&#xff1a;

make_shared知识点

背景 普通创建shared_ptr的方法如&#xff1a; shared_ptr<int> sp1(new int(11));sp1所开辟的动态内存分为如下两部分 uses是持有该资源shared_ptr数量&#xff0c;weaks表示持有该资源weak_ptr数量。 有可能出现 new int(10) 成功&#xff0c;但是引用计数 ref 的内…