【JavaEE初阶】多线程(3)

news2024/12/27 11:08:50

欢迎关注个人主页:逸狼


创造不易,可以点点赞吗~

如有错误,欢迎指出~



目录

线程状态

线程安全

代码示例 

解释

总结原因

解决方案-->加锁

t1和t2都加锁 且 同一个锁对象

t1和t2中只有一个加锁了

t1和t2都加锁,但锁对象不同

加锁 与线程等待(join) 的区别

使用类对象加锁

加锁场景

死锁

场景1

场景2

​编辑场景3

构成死锁的4个必要条件(缺一不可)

解决方案举例

方案1 避免锁嵌套

方案2 约定加锁顺序

方案3 银行家算法


线程状态

进程状态 分为两种:

  • 就绪:正在cpu上执行,或者随时可以去cpu上执行
  • 阻塞:暂时不能参与cpu执行

Java的线程状态有 6 种

  1. NEW  当前Thread对象虽然有了,但是内核的线程还没有(还没调用start)
  2. TERMINATED 当前Thread对象虽然还在,但是内核线程已经销毁(线程已经结束)
  3. RUNNABLE  就绪状态,正在cpu上运行 +  随时可以去cup上运行
  4. BLOCKED  因为锁竞争引起的阻塞
  5. TIMED_WAITING  有超时的等待 如sleep,或者join带参数版本
  6. WAITING  没有超时 时间的阻塞等待 如 join/wait

学习线程状态主要为了 调试,比如 遇到某个代码功能没有执行,就可以观察对应线程的状态,看是否是因为一些原因阻塞了.

线程安全

多个线程同时执行某个代码时,可能会引起一些奇怪的bug,理解线程安全才能 避免/解决 上述bug

代码示例 

public class Demo12 {
    public static int count=0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("count="+ count);
    }
}

因为多线程并发执行 引发的bug称为"线程安全问题" 或"线程不安全"

解释

上述代码count++;在cpu视角是3个指令:

  1. load   把内存中的数据读到cpu寄存器里
  2. add    把cpu寄存器里的数据+1
  3. sava  把寄存器的值,写回内存

指令是cpu执行的基本单位,如果要调度,cup至少会把当前指令执行完,

cpu调度执行线程的方式是 抢占式执行,随机调度,

但是由于count++ 是三个指令,可能会出现cpu执行了其中1个指令或2个指令或3个指令就被调度走的情况(都有可能,无法预测), 所以 两个线程同时对count进行++ 就容易出现bug

由于循环5w次过程中不知道有多少次的执行顺序是前两种正确情况,有多少次是其他错误情况,最终的结果就是一个不确定的值,而这个值 一定小于10w

但结果也有可能小于5w

总结原因

  • 线程在操作系统中,随机调度,抢占式执行(根本原因), 此原因无法干预(操作系统内核,作为应用层的程序员无法干预)
  • 多个线程,同时修改一个变量(如果是一个线程修改,就没事)
  • 修改操作,不是"原子"的,(对cpu来说,一条指令才是"原子"的,是不可分割的最小的单位)

解决方案-->加锁

解决线程安全问题,最主要的方法就是 把"非原子" 的修改,变成"原子"(通过加锁,把非原子的修改操作 打包成一个整体,变成原子操作)

t1和t2都加锁 且 同一个锁对象

此处的加锁,没有干预到线程的调度,只是通过加锁,使一个线程在执行count++时,其他线程的count++不能插队进来

Java提供synchronized关键字 来完成加锁操作,synchronizede()的'()'中需要指定一个 "锁对象" (可以指定任何对象)来进行后面的判定

t1和t2都是针对locker对象加锁,t1加锁成功后,继续执行{}里的代码,t2后加锁,发现locker对象已经被别人先锁了,t2只能排队等待(这两者的++ 操作不会并发执行了,本质上是把随机并发的执行过程 强制变成了串行,从而解决了刚才的线程安全问题)

t1和t2中只有一个加锁了

t1和t2都加锁,但锁对象不同

锁对象的作用 就是用来区分 两个线程或多个线程 是否针对"同一个对象"加锁

  • 若是,此时就会出现阻塞(锁竞争/锁冲突)
  • 若不是,此时不会出现"阻塞",两个线程仍然是 随机调度的并发执行.

锁对象,只要是Object(或者其子类)都行,不能是int,double这样的内置类型

加锁 与线程等待(join) 的区别

上述加锁后的代码 本质上要比join的串行执行 的效率还要高

  • 加锁只是把线程中一小部分逻辑 变成了 串行执行,剩下其他部分仍然可以并发执行
  • join是 线程 整体都串行执行

使用类对象加锁

一个Java进程中,一个类的类对象是只有唯一一个的,类对象,也是对象,也可以成为锁对象,写类对象和写其他对象 没有本质区别,换句话说,写成类对象,就是'偷懒'的做法(不想单独创建锁对象了~)

 

加锁场景

是否要加锁,怎么加锁,都是和具体场景直接相关的("无脑加锁"是不推荐的)

锁 ,需要的时候才使用,不需要的时候不要使用,否则会付出代价(性能)

使用锁,就可能会发生阻塞,一旦某个线程阻塞,啥时候恢复阻塞 继续执行是不可预期的~

死锁

场景1

一个线程,针对一把锁,连续加锁两次

Java的synchronized做了特殊处理(引入了特殊机制,"可重入锁"),不会出现 死锁,但同样的代码换成c++/python就会死锁

可重入锁就是在锁中 额外记录一下 当前是哪个线程,对哪个锁加锁了,后续加锁时就会进行判定

还会引入一个引用计数,维护当前已经加锁几次了,并且描述何时真正释放锁

场景2

两个线程,两把锁

  1. 线程1先针对A加锁,线程2针对B加锁
  2. 线程1不释放锁A的情况下,在针对B加锁,同时线程2不释放锁B的情况下对A加锁

这种情况,可重入锁 也无能为力~

package thread;

public class Demo17 {
    private static Object locker1 = new Object();
    private static Object locker2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (locker1) {
                System.out.println("t1 加锁 locker1 完成");

                // 这里的 sleep 是为了确保, t1 和 t2 都先分别拿到 locker1 和 locker2 然后在分别拿对方的锁.
                // 如果没有 sleep 执行顺序就不可控, 可能出现某个线程一口气拿到两把锁, 另一个线程还没执行呢, 无法构造出死锁.
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                synchronized (locker2) {
                    System.out.println("t1 加锁 locker2 完成");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (locker2) {
                System.out.println("t2 加锁 locker2 完成");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                synchronized (locker1) {
                    System.out.println("t2 加锁 locker1 完成");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

场景3

N个线程,M个锁 

经典问题:哲学家就餐问题

当每根筷子都被哲学家左手拿起来了,他们右手就没有筷子可以拿了,当哲学家吃不到面条时,也就不会放下左手的筷子, 此时就产生了 死锁.

构成死锁的4个必要条件(缺一不可)

锁的基本特性:

  • 1.锁是互斥的 . 如 一个线程拿到锁,另一个线程就拿不到这个锁
  • 2.锁是不可被抢占的.  如 线程1拿到了锁A,若线程1不主动释放A,线程2不能把锁A抢过来

对于synchronized 这样的锁,互斥和不可抢占都是基本特性, 无法干预

代码结构上:

  • 3.请求 和 保持. 如 线程1拿到锁A之后,不释放A的前提下,去拿锁(解决方法: 如果是 先释放A,再拿B,不会有问题)
  • 4.循环等待 / 环路等待 / 循环依赖. 如 多个线程获取锁时,存在 循环等待( 解决方法:如果在获取多把锁的时候,不要构成循环等待就行了)

解决方案举例

针对场景2,通过改变代码结构 解决死锁问题(对症下药~)

方案1 避免锁嵌套

针对 死锁构成条件3 

方案2 约定加锁顺序

针对 死锁构成条件4 

给锁进行编号1,2,3,4...N,约定所有的线程在加锁时都必须按照一定的顺序加锁(比如,必须先针对编号小的锁,加锁,后对大的加锁)

方案3 银行家算法

但是银行家算法太复杂了,如果在日常开放中,实现一套银行家算法解决死锁,先不说死锁的问题是否存在,你实现的银行家算法本身可能存在bug~

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

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

相关文章

我给孩子请了个AI老师,省掉了1999元的报名费

大家好&#xff0c;我是凡人。 最近老婆想给儿子在线报个书法班&#xff0c;要价1999元&#xff0c;本来是个好事情&#xff0c;但一向勤俭持家的我&#xff0c;怎能让她花这个冤枉钱&#xff0c;经过我三七二十一个小时的上网&#xff0c;还真让我找出一套利用AI学习的万能命…

图片无损放大编辑PhotoZoom Pro 9.0.4多版本软件2024年最新安装包下载含安装教程

PhotoZoom Pro 9.0.4是一款非常流行的图像放大软件&#xff0c;它可以让你将低分辨率的图像放大到高分辨率的尺寸&#xff0c;同时保持高质量的图像细节和清晰度。 PhotoZoom Pro 9.0.4采用了一种称为S-Spline技术的算法&#xff0c;这是一种能够保持图像细节的高级插值算法。…

Web3 详解

1. 使用 Web3 库 Web3 是一个 JavaScript 库&#xff0c;可用于通过 RPC 通信与以太坊节点通信。 Web3 的工作方式是&#xff0c;公开已通过 RPC 启用的方法&#xff0c;这允许开发利用 Web3 库的用户界面&#xff0c;以便与部署在区块链上的合约进行交互。 一旦 Geth JavaScri…

25届计算机专业选题推荐-基于python的线上拍卖会管理系统【python-爬虫-大数据定制】

&#x1f496;&#x1f525;作者主页&#xff1a;毕设木哥 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; 实战项目 文章目录 实战项目 一、基于python的线上拍卖会管理…

Window下编译OpenJDK17

本文详细介绍Window下如何编译OpenJDK17&#xff0c;包含源码路径&#xff0c;各工具下载地址&#xff0c;严格按照文章中的步骤来操作&#xff0c;你将获得一个由自己亲手编译出的jdk。 一、下载OpenJDK17源码 下载地址&#xff1a;GitHub - openjdk/jdk at jdk-1735 说明&a…

碰撞检测 | 详解矩形AABB与OBB碰撞检测算法(附ROS C++可视化)

引言 在复杂的人工智能系统和机器人应用中,碰撞检测(Collision Detection)作为一项基础技术,扮演着至关重要的角色。无论是在自动驾驶车辆中防止车祸的发生,还是在机器人导航中避免障碍物,碰撞检测的精度和效率都直接决定了系统的可靠性和安全性。在游戏开发、虚拟现实、…

USART—串口通讯

USART—串口通讯 大纲 串口通讯协议简介STM32 的 USART 简介USART 功能框图USART 初始化结构体详解 具体案例 串口通讯协议简介 物理层 串口通讯的物理层有很多标准及变种&#xff0c;我们主要讲解 RS-232 标准&#xff0c;RS-232 标准主要规定了信号的用途、通讯接口以及…

SpringCache之本地缓存

针对不同的缓存技术&#xff0c;需要实现不同的cacheManager&#xff0c;Spring定义了如下的cacheManger实现。 CacheManger 描述 SimpleCacheManager 使用简单的Collection来存储缓存&#xff0c;主要用于测试 ConcurrentMapCacheManager 使用ConcurrentMap作为缓存技术&…

spring揭秘20-spring事务02-编程式事务与声明式事务管理

文章目录 【README】【1】编程式事务管理【1.1】使用PlatformTransactionManager进行编程式事务管理【1.2】使用TransactionTemplate进行编程式事务管理【1.3】基于Savepoint的嵌套事务 【2】声明式事务管理【2.1】基于xml的声明式事务【2.1.1】使用ProxyFactory&#xff08;Pr…

【基础篇】应届毕业生必备:机器学习面试题指南【1】

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发…

【软件工程】软件开发模型

三、瀑布模型 四、几种软件开发模型的主要特点 题目 判断题 选择题 小结

房贷计算及比较

本博客主要介绍&#xff1a; 1. 等额本金计算公式 2. 等额本息计算公式 3. 对比两种还款方式 4. 本示例:贷款金额为35万&#xff0c; 期限12年&#xff0c;年利率4.9% 等额本金计算 import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams[font.sans-s…

day1 QT

作业 #include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {//设置窗口大小this->resize(1025,533);//固定窗口大小this->setFixedSize(1025,533);//设置窗口背景色,设置弧度//this->setStyleSheet("background-image:url(E:/…

JS_数据类型

一、JS的数据类型 数值类型 数值类型统一为 number,不区分整数和浮点数 字符串类型 字符串类型为 string 和JAVA中的String相似,JS中不严格区分单双引号,都可以用于表示字符串 布尔类型 布尔类型为boolean 和Java中的boolean相似,但是在JS的if语句中,非空字符串会被转换为…

SuperMap iManger 单个镜像更新流程

1. 下载镜像 docker pull registry.cn-beijing.aliyuncs.com/supermap/common-dashboard-api:11.1.1-240802-amd64 label 没区分架构&#xff0c;在 x64 环境 pull arm64 镜像 通过 --platformarm64 参数可以实现 docker pull mariadb:10.5.26 --platformarm64 # 指定拉取ar…

【重学 MySQL】十九、位运算符的使用

【重学 MySQL】十九、位运算符的使用 示例检查权限添加权限移除权限 在 MySQL 中&#xff0c;位运算符允许你直接在整数类型的列或表达式上进行位级操作。这些操作对于处理那些需要在二进制表示上进行直接修改或比较的场景特别有用&#xff0c;比如权限管理、状态标记等。 &…

20240909 每日AI必读资讯

重磅&#xff01;TIME揭榜2024全球AI 100人&#xff1a;奥特曼、黄仁勋、姚期智、王小川等上榜 - TIME正式揭晓了第二届100位最具影响力AI人物名单&#xff01;「领导者」榜单中&#xff0c;有我们耳熟能详的OpenAI CEO Sam Altman、英伟达CEO黄仁勋。而字节跳动联合创始人兼C…

数据结构(邓俊辉)学习笔记】排序 7——希尔排序:Shell 序列 + 输入敏感性

文章目录 1.邮资问题2. 定理K3.逆序对 1.邮资问题 此前曾经讲到希尔排序在对矩阵逐列排序时所使用的算法本身未必需要十分高效&#xff0c;而更重要的是应该具有输入敏感的特性&#xff0c;因此我们更倾向于使用插入排序。那么背后的具体原因又当如何解释呢&#xff1f;这里的…

ubuntu16.04 vim使用中文出现乱编文档处理

问题现象 vim 编译文件时出现乱码问题 解决方法 1. 中文语言包安装: apt-get install language-pack-zh-hans 2. 配置环境变量:echo "export LC_ALLzh_CN.UTF-8" >>/etc/bash.bashrc 3. 修改当前环境的字符集 /etc/default/locale cat /etc/default/locale…

国内外网络安全政策动态(2024年8月)

▶︎ 1.《关于进一步加强智能网联汽车准入、召回及软件在线升级管理的通知》公开征求意见 8月1日&#xff0c;工业和信息化部装备工业一司联合市场监管总局质量发展局组织编制了《关于进一步加强智能网联汽车准入、召回及软件在线升级管理的通知&#xff08;征求意见稿&#…