当前位置: 首页 > news >正文

【JavaEE】多线程(2)

c95cbc70da094545b55d50d33cc484d4.png


一、线程安全

1.1 线程安全的概念

线程是随机调度执行的,如果多线程环境下的程序运行的结果符合我们预期则说明线程安全,反之,如果遇到其他结果甚至引起了bug则说明线程不安全

1.2 经典例子与解释

下面举一个经典的线程不安全的例子:

public class Demo2 {private 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);}
}

上述代码中t1和t2两个线程对count进行累加操作,在主线程中启动这两个线程然后通过join等待这两个线程都执行完后打印count,预期结果为100000,但打印结果如下:

759abc0e6c384d009e620f0c28defba8.png

上述结果不符合我们的预期,这便是产生了线程安全问题

接下来我们通过CPU指令的方式解释上述原因

count++这个行代码可以看作3个CPU指令:

  1. 把内存count总的值读取到CPU寄存器中 => load
  2. 把寄存器中的值+1,此时任然在寄存器中 =>add
  3. 把上述寄存器计算后的值写回到内存count里 =>save

由于线程随机调度,所以两个线程的CPU指令执行顺序也是随机的。

例如下图:

(画图,时间轴。。。。。。。。。。)

首先t1线程和t2线程分别将1加载到CPU寄存器中(假设此时count的值为1),然后在寄存器中将其加1变为2,最后t1先将2加载回内存中,t2也把2加载回内存中,所以两次加1操作只加了一次1

当然上述执行顺序只是无数可能中的一种,可能t1的一组指令还没有执行完,t2就执行了好几组

下面来总结一下线程不安全的原因

1.3 线程不安全的原因

  1. 线程是随机调度,抢占式执行的
  2. 修改共享数据,多个线程修改同一个变量
  3. 多个线程修改共享数据的操作不是原子性,(count++是3个CPU指令,但是赋值操作就是原子性的)
  4. 内存可见性问题
  5. 指令重排序

4和5后面再解释

1.4 解决线程安全问题

根据上述原因下手

原因1:无法干预

原因2:可以干预,但并不是一个普适的做法,因为有些代码就是要修改同一个变量

原因3:这是一个普适的做法,我们可以将一系列非原子的操作打包成一个原子性的操作->加锁

1.4.1什么是锁

锁是在多线程编程中用来控制线程对共享资源访问的一种机制

1.针对锁主要有这两个操作:

  • 加锁:线程t1加上锁之后,t2也尝试使用同一个锁进行加锁,就会阻塞等待

问:什么叫“t2也尝试使用同一个锁进行加锁”?

答:你可以理解为我们给t2里的操作加上了一种机制,这个机制就是必须加上锁才能进行操作,t1拿了一个锁,加锁后进行它的操作,如果t2也想拿这个锁来加锁就必须等t1操作完成解锁之后,再拿这个锁进行加锁进行它的操作,在此之前t2要阻塞等待,当然,如果t2选择拿别的锁进行加锁就不会阻塞等待(假设只有t1和t2两个线程)

比如,A在餐厅里定了一个包间,把门上锁之后来用餐,这样B来了就不会影响A用餐的过程;也就是t2不会对t1修改count的过程进行干扰,这样就保证了操作的原子性

  • 解锁:t1解锁之后,t2才有可能拿到锁,因为尝试竞争锁的线程可能不只一个

2. 锁的主要特性:互斥,一个线程获取到锁之后,另一个线程也尝试加这个锁,就会阻塞等待,这种现象叫锁竞争锁冲突,代码中也可以有多个锁,只有多个线程竞争同一个锁才会发生锁竞争,竞争不同的锁则不会发生锁竞争

1.5 synchronized关键字

1.5.1 synchronized解读

使用synchronized关键字,synchronized关键字解读:

synchronized (locker) {count++;
}
  1. 这是一个Java的关键字,不是方法
  2. synchronized后面括号里面写的是锁对象
  3. 锁对象的用途:用来区分两个线程是否针对同一个对象加锁,如果是,就会出现锁竞争/互斥就会引起阻塞等待,如果不是就不会出现锁竞争,也就不会阻塞等待
  4. synchronized的{ }:进入到代码块,就是对上述锁对象进行加锁操作,当出了代码块,就是对锁对象进行解锁

我们可以让t1和t2都使用同一个锁对象locker来对count变量的修改操作进行上锁

private static int count = 0;
public static void main(String[] args) throws InterruptedException {Object locker = new Object(); //锁对象Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (locker) {count++;}}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {synchronized (locker) {count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println("count = "+ count);
}

这样,虽然两个线程仍然是抢占式执行的,但是保证了count++;这个操作的原子性,结果为:count = 100000 

Java中随便拿一个对象,都可以作为加锁的对象

 1.5.2 synchronized使用示例

1)修饰代码块:指定锁哪个对象,也就是可以锁任意对象

public class SynchronizedDemo {private Object locker = new Object();public void method() {synchronized (locker) {}}
}

锁当前对象:()里直接写this

public class SynchronizedDemo {public void method() {synchronized (this) {}}
}

2)修饰普通方法:锁的SynchronizedDemo对象,谁调用method()方法,就锁谁(可以有多个)

public class SynchronizedDemo {public synchronized void methond() {}
}

3)修饰静态方法:锁的SynchronizedDemo类对象(一个java进程中,一个类只有唯一一个类对象)

public class SynchronizedDemo {public synchronized static void method() {}
}

1.5.3 synchronized特性

1)互斥

某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同一个对象,synchronized就会阻塞等待

2)可重入

for (int i = 0; i < 50000; i++) {synchronized (locker) {synchronized (locker) {count++;}}
}

上述线程先对locker进行第一次加锁,在第二次加锁的时候,locker对象已经被锁住了,按照之前的理解,尝试针对一个已经被锁的对象加锁时,就会阻塞等待,这种情况就叫死锁

但synchronized是可重入锁,可重入锁的内部包含了线程持有者计数器

  • 如果某个线程加锁的时候,发现这个锁已经被别人占用,但是恰好占用的是自己,那么仍然可以继续获取到锁,并让计数器自增
  • 解锁的时候(也就是每走出一个代码块)计数器就会递减,当减到0时才真正释放锁

这种机制就叫可重入锁

1.6 死锁

1.6.1 两个常见的场景

死锁有两个比较典型的场景

场景一:不可重入锁引起的死锁

一个线程对一个线程连续加锁两次且这个锁是不可重入锁就会引起死锁

场景二:两个线程两把锁

    public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized(locker1) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2) {System.out.println("t1获取了两把锁");}}});Thread t2 = new Thread(() -> {synchronized(locker2) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1) {System.out.println("t2获取了两把锁");}}});t1.start();t2.start();System.out.println("死锁ing....");}

上述代码中,locker1被t1占用,locker2被t2占用,接下来t1需要locker2,t2需要locker1,这样就陷入了死锁

运行结果显示程序一直没有结束:

1.6.2 如何避免死锁

死锁产生的四个必要条件:

1)锁具有互斥性

2)锁不可抢占:一个线程拿到锁之后,除非它主动释放锁,否则别人抢不走

以上这两点是锁的基本特性,无法干预

3)请求和保持:一个线程拿到一把锁之后,不释放这个锁的前提下,在尝试获取其他锁(嵌套加锁)

解决方法就是不要让两个sychronized嵌套式的占用两个不同的锁对象进行加锁

4)循环等待:多个线程获取多个锁的过程中,出现了循环等待,A等待B,B又等待A

这一点只要我们提前约定好获取锁的顺序,即使出现了嵌套也不会引起死锁,如下述代码t1和t2线程都先获取locker1再获取locker2,这样就不会出现死锁

    public static void main(String[] args) throws InterruptedException {Object locker1 = new Object();Object locker2 = new Object();Thread t1 = new Thread(() -> {synchronized(locker1) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2) {System.out.println("t1获取了两把锁");}}});Thread t2 = new Thread(() -> {synchronized(locker1) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2) {System.out.println("t2获取了两把锁");}}});t1.start();t2.start();}

任何一个死锁的场景,都必须同时具备上述四点,缺少一点都不会构成死锁


🙉本篇文章到此结束,下篇文章将继续对线程安全的知识进行讲解

相关文章:

【JavaEE】多线程(2)

一、线程安全 1.1 线程安全的概念 线程是随机调度执行的&#xff0c;如果多线程环境下的程序运行的结果符合我们预期则说明线程安全&#xff0c;反之&#xff0c;如果遇到其他结果甚至引起了bug则说明线程不安全 1.2 经典例子与解释 下面举一个经典的线程不安全的例子&…...

mac下Gpt Chrome升级成GptBrowser书签和保存的密码恢复

cd /Users/自己的用户名/Library/Application\ Support/ 目录下有 GPT\ Chrome/ Google/ GptBrowser/ GPT\ Chrome 为原来的chrome浏览器的文件存储目录. GptBrowser 为升级后chrome浏览器存储目录 书签所在的文件 Bookmarks 登录账号Login 相关的文件 拷贝到GptBrow…...

使用Grafana K6来测测你的系统负载能力

背景 近期我们有个号称会有很高很高并发的系统要上线&#xff0c;为了测试一下自己开发的系统的负载能力&#xff0c;准备了点海克斯科技&#xff0c;来看看抗不抗的住。 之前笔者写过用Apache JMeter进行压力测试的文章&#xff08;传送门&#x1f449;&#xff1a;https://…...

【论文复现】基于BERT的语义分析实现

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀ WRN: 宽度残差网络 概述语义分类文本分类情感分类 实现原理 核心逻辑pre_deal.pytrain.pytest_demo.py 实现方式&演示效果训练阶段测试阶…...

CTF-RE: STL逆向 [NewStarCTF 2023 公开赛道 STL] WP

多看看STL题就会了,很简单 int __fastcall main(int argc, const char **argv, const char **envp) {__int64 v3; // rbx__int64 v4; // raxchar v5; // bl_BYTE *v6; // rax_QWORD *v7; // rax__int64 v8; // rax__int64 v9; // raxint i; // [rsp0h] [rbp-250h]int j; // [r…...

实习冲刺第三十六天

46.全排列 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#xff1a; 输入&#…...

【Zemax光学设计实训三】---激光缩束镜的设计优化

前言与目录 技术设计要求&#xff1a; 设计一个激光扩束镜&#xff0c;使用的波长为1064nm&#xff0c;输入光束直径为10mm&#xff0c;输出光束的直径为2mm&#xff0c;且输入光束和输出光束平行&#xff08;即平行光入射&#xff0c;平行光出射&#xff09;。要求只使用两片…...

TCP/IP协议簇自学笔记

摘抄于大学期间记录在QQ空间的一篇自学笔记&#xff0c;当前清理空间&#xff0c;本来想直接删除掉的&#xff0c;但是感觉有些舍不得&#xff0c;因此先搬移过来。 曾经&#xff0c;我只知道socket函数能进行网络间数据的通信&#xff0c;知道tcp/ip协议也是用来进行网络数据…...

Spring Boot教程之十一:获取Request 请求 和 Put请求

如何在 Spring Boot 中获取Request Body&#xff1f; Java 语言是所有编程语言中最流行的语言之一。使用 Java 编程语言有几个优点&#xff0c;无论是出于安全目的还是构建大型分发项目。使用 Java 的优点之一是 Java 试图借助类、继承、多态等概念将语言中的每个概念与现实世…...

计算机网络(二)

ip地址&#xff1a;11010010&#xff1a;01011110:00100100:00010100 子网掩码:11111111:11111111:11111111:11000000 and &#xff1a;11010010&#xff1a;01011110&#xff1a;00100100&#xff1a;00000000 210.94.36.0的下一站为R1 因为255为11111111 192为&#xff…...

如何在Python中进行数学建模?

数学建模是数据科学中使用的强大工具&#xff0c;通过数学方程和算法来表示真实世界的系统和现象。Python拥有丰富的库生态系统&#xff0c;为开发和实现数学模型提供了一个很好的平台。本文将指导您完成Python中的数学建模过程&#xff0c;重点关注数据科学中的应用。 数学建…...

JavaSE——类与对象(5)

一、抽象类 1.1为什么需要抽象类 父类的某些方法&#xff0c;不确定怎么实现&#xff0c;也不需要实现。 class Animal{public String name;public Animal(String name){this.name name;}public void eat()//这里实现了也没有意义{System.out.println("这是一个动物&am…...

Istio笔记01--快速体验Istio

Istio笔记01--快速体验Istio 介绍部署与测试部署k8s安装istio测试istio 注意事项说明 介绍 Istio是当前最热门的服务网格产品&#xff0c;已经被广泛应用于各个云厂商和IT互联网公司。企业可以基于Istio轻松构建服务网格&#xff0c;在接入过程中应用代码无需更改&#xff0c;…...

面试小札:Java如何实现并发编程

多线程基础 继承Thread类 定义一个类继承自 Thread 类&#xff0c;重写 run 方法。在 run 方法中编写线程要执行的任务逻辑。例如&#xff1a; java class MyThread extends Thread { Override public void run() { System.out.println("线程执行的任务…...

java-a+b 开启java语法学习

代码 &#xff08;ab) import java.util.Scanner; //导入 java.util包中的Scanner 类&#xff0c;允许读取键盘输入数据public class Main { // 创建一个公共类 Mainpublic static void main(String[] args) {//程序入口点&#xff0c;main方法Scanner scanner new Scanner(…...

RNN模型文本预处理--数据增强方法

数据增强方法 数据增强是自然语言处理&#xff08;NLP&#xff09;中常用的一种技术&#xff0c;通过生成新的训练样本来扩充数据集&#xff0c;从而提高模型的泛化能力和性能。回译数据增强法是一种常见的数据增强方法&#xff0c;特别适用于文本数据。 回译数据增强法 定义…...

maven 中<packaging>pom</packaging>配置使用

在 Maven 项目的 pom.xml 文件中&#xff0c; 元素用于指定项目的打包类型。默认情况下&#xff0c;如果 元素没有被显式定义&#xff0c;Maven 会假设其值为 jar。但是&#xff0c;当您设置 pom 时&#xff0c;这意味着该项目是一个 POM&#xff08;Project Object Model&…...

【Python中while循环】

一、深拷贝、浅拷贝 1、需求 1&#xff09;拷贝原列表产生一个新列表 2&#xff09;想让两个列表完全独立开&#xff08;针对改操作&#xff0c;读的操作不改变&#xff09; 要满足上述的条件&#xff0c;只能使用深拷贝 2、如何拷贝列表 1&#xff09;直接赋值 # 定义一个…...

【深度学习】服务器常见命令

1、虚拟环境的安装位置 先进入虚拟环境 which python2、升序查看文件内容 ls -ltr3、查看服务器主机空间使用情况 df -hdf -h .4、查看本地空间使用情况 du -sh ./*du -sh * | sort -nr5、查找并删除进程 # 查找 ps aux# 删除 kill -KILL pid6、查看服务器配置 lscpuuna…...

技术分析模板

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 提示&#xff1a;这里可以添加技术概要 例如&#xff1a; openAI 的 GPT 大模型的发展历程。 整体架构流程 提示&#xff1a;这里可以添加技术整体架构 例如&#xff1a; 在语言模型中&#xff0c;编码器和解码器…...

python:文件操作

一、文件路径 在Windows系统中&#xff0c;每个磁盘都有自己的根目录&#xff0c;用分区名加反斜杠来表示。我们定位文件的位置有两种方法&#xff0c;一种是绝对路径&#xff0c;另一种是相对路径。绝对路径是从根目录出发的路径&#xff0c;路径中的每个路径之间用反斜杠来分…...

Nginx和Apache有什么异同?

Nginx和Apache都是广泛使用的Web服务器软件&#xff0c;它们各自具有独特的特点和优势&#xff0c;适用于不同的应用场景。以下是关于Nginx和Apache的不同、相同以及使用区别的详细分析&#xff1a; 一、不同点 资源占用与并发处理能力&#xff1a; Nginx使用更少的内存和CPU资…...

泰州榉之乡全托机构探讨:自闭症孩子精细动作训练之法

当发现自闭症孩子精细动作落后时&#xff0c;家长们往往会感到担忧和困惑。那么&#xff0c;自闭症孩子精细动作落后该如何训练呢&#xff1f;今天&#xff0c;泰州榉之乡全托机构就来为大家详细解答。 榉之乡大龄自闭症托养机构在江苏、广东、江西等地都有分校&#xff0c;一直…...

Cookie跨域

跨域&#xff1a;跨域名&#xff08;IP&#xff09; 跨域的目的是共享Cookie。 session操作http协议&#xff0c;每次既要request&#xff0c;也要response&#xff0c;cookie在创建的时候会产生一个字符串然后随着response返回。 全网站的各个页面都会带着登陆的时候的cookie …...

qt QGraphicsPolygonItem详解

1、概述 QGraphicsPolygonItem是Qt框架中QGraphicsItem的一个子类&#xff0c;它提供了一个可以添加到QGraphicsScene中的多边形项。通过QGraphicsPolygonItem&#xff0c;你可以定义和显示一个多边形&#xff0c;包括其填充颜色、边框样式等属性。QGraphicsPolygonItem支持各…...

“harmony”整合不同平台的单细胞数据之旅

其实在Seurat v3官方网站的Vignettes中就曾见过该算法&#xff0c;但并没有太多关注&#xff0c;直到看了北大张泽民团队在2019年10月31日发表于Cell的《Landscap and Dynamics of Single Immune Cells in Hepatocellular Carcinoma》&#xff0c;为了同时整合两类数据&#xf…...

如何构建一个可扩展、全球可访问的 GenAI 架构?

你有没有尝试过使用人工智能生成图像&#xff1f; 如果你尝试过&#xff0c;你就会知道&#xff0c;一张好的图像的关键在于一个详细具体的提示。 我不擅长这种详细的视觉提示&#xff0c;所以我依赖大型语言模型来生成详细的提示&#xff0c;然后使用这些提示来生成出色的图像…...

QT实战--qt各种按钮实现

本篇介绍qt一些按钮的实现&#xff0c;包括正常按钮&#xff1b;带有下拉箭头的按钮的各种实现&#xff1b;按钮和箭头两部分分别响应&#xff1b;图片和按钮大小一致&#xff1b;图片和按钮大小不一致的处理&#xff1b;文字和图片位置的按钮 效果图如下&#xff1a; 详细实现…...

RNN And CNN通识

CNN And RNN RNN And CNN通识一、卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;CNN&#xff09;1. 诞生背景2. 核心思想和原理&#xff08;1&#xff09;基本结构&#xff1a;&#xff08;2&#xff09;核心公式&#xff1a;&#xff08;3&#xff09;关…...

生产环境中:Flume 与 Prometheus 集成

在生产环境中&#xff0c;将 Apache Flume 与 Prometheus 集成的过程&#xff0c;需要借助 JMX Exporter 或 HTTP Exporter 来将 Flume 的监控数据转换为 Prometheus 格式。以下是详细的实现方法&#xff0c;连同原理和原因进行逐步解释&#xff0c;让刚接触的初学者也能完成集…...

沈阳网站搜索排名/百度广告登录入口

最后贴一张小米官方拆机零件汇总图片&#xff1a;软件篇&#xff1a;小米路由器的系统是在开源OpenWRT的基础上进行了定制。界面交互更加友好。经过几天的试用&#xff0c;《假装是极客》感受最深的是&#xff0c;小米路由的APP与小米系列硬件已经深度集成&#xff0c;如果是米…...

网址大全hao123/seo策略什么意思

coursera上的公开课《https://www.coursera.org/course/textanalytics》系列&#xff0c;讲的非常不错哦。 最后讲了文本分类&#xff0c;和plsa的几种变形&#xff0c;包括&#xff1a; opinion mining和sentiment analysis&#xff1a;Ordinal Logistic Regression opinion…...

柳州住房和城乡建设厅网站/seo平台有哪些

这篇文章我想写给做IDC的朋友以及购买空间的朋友看的&#xff0c;因为关于这个问题一直以来就有很多含糊与误会的地方。我要提出的观点是&#xff0c;网站空间参数配置里写的IIS连接数并不等同于支持的并发在线人数。 相信做过IDC的朋友都碰见过这样的事情&#xff0c;经常有…...

做钓鱼网站查处/二级域名注册平台

vector&#xff08;向量&#xff09; 数据结构&#xff1a;一个可变长空间的数组&#xff0c;内存中连续存放。优点&#xff1a;遍历效率高&#xff0c;数据插入尾部效率也高。缺点&#xff1a;数据插入或删除时&#xff0c;如果操作的位置不在末尾效率比较底。在同等容器中优…...

网络工作室适合做什么/站长工具seo优化

最近跟一位牛人学java项目的搭建&#xff0c;才知道这个EGit的功能很强大。安装的话就参考这个下面的连接http://www.cnblogs.com/zhxiaomiao/archive/2013/05/16/3081148.html详细的有关具体的操作指示请看下面两个链接&#xff1a;https://www.eclipse.org/egit/http://www.v…...

杭州亚运村建设指挥部网站/个人怎么开跨境电商店铺

刚开始学习C#的时候就写过了&#xff0c;直接给地址了&#xff1a; 委托、匿名函数、Lambda表达式和事件的学习 委托学习续&#xff1a;Action、Func和Predicate...