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

Java之线程篇三

​​​​​​​

目录

线程状态

观察线程的所有状态

线程状态及其描述

线程状态转换

代码示例1

代码示例2

线程安全 

概念

线程不安全的代码示例

线程不安全的原因

线程安全的代码示例-加锁

synchronized关键字

synchronized的特性

小结

形成死锁的四个必要条件

synchronized的使用示例

Java标准库中的线程安全类


线程状态
观察线程的所有状态

线程的状态是一个枚举类型 Thread.State

public class Demo11 {public static void main(String[] args) {for(Thread.State state:Thread.State.values())System.out.println(state);}
}

运行结果

线程状态及其描述

NEW:Thread对象已经有了,但是start方法还没调用;

RUNNABLE:就绪状态,线程已经在CPU上执行了/线程正在排队等待执行(即工作中或即将开始工作);

TERMINATED:Thread对象还在,但是内核中的下线程已经没了,即工作完成了;

TIMED_WARTING:阻塞状态,由于sleep这种固定时间的方式产生的阻塞;

WAITING:阻塞,由于wait这种不固定时间的方式产生的阻塞;

BLOCKED:阻塞,由于锁竞争导致的阻塞。

线程状态转换

代码示例1
public class Demo11 {public static void main(String[] args) {Object object=new Object();Thread t1=new Thread(new Runnable() {@Overridepublic void run() {synchronized (object){while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}},"t1");t1.start();Thread t2=new Thread(new Runnable() {@Overridepublic void run() {synchronized (object){System.out.println("hello");}}},"t2");t2.start();}
}

通过jconsole可以看到t1的状态是TIMED_WAITING,t2的状态是BLOCKED。

代码示例2
public class Demo11 {public static void main(String[] args) {Object object=new Object();Thread t1=new Thread(new Runnable() {@Overridepublic void run() {synchronized (object){while(true){try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}},"t1");t1.start();}
}

通过jconsole可以看到t1的状态是WAITING.

小结

BLOCKED表示等待获取锁,WAITING和TIMED_WAITING表示等待其它线程发来通知;

TIMED_WAITING线程在等待唤醒,但设置了时限;

WAITING线程在无限等待唤醒。

线程安全 
概念

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

线程不安全的代码示例
public class Demo12 {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);}
}

运行结果

结果与预期结果不一致且差别很大,显然上述代码是线程不安全的。

线程不安全的原因

1.修改共享数据

上述代码涉及到多线程(两个及两个以上的线程)针对同一个变量count进行修改。

2.原子性

一条Java语句不一定是原子的,也不一定只是一条指令。

比如count++,其实是由三步操作组成的:

1.从内存中把数据读取到CPU;

2.对变量count进行++;

3.把数据写回到内存。

如果一个线程正在对一个变量操作,中途其它线程插入进来了,如果这个操作被打断,结果就可能是错误的。

3.可见性

可见性指, 一个线程对共享变量值的修改,能够及时地被其他线程看到.

Java 内存模型 (JMM): Java虚拟机规范中定义了Java内存模型. 
目的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果.

线程之间的共享变量存在 主内存 (Main Memory). 
每一个线程都有自己的 "工作内存" (Working Memory) . 
当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据. 
当线程要修改一个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存. 

由于每个线程有自己的工作内存, 这些工作内存中的内容相当于同一个共享变量的 "副本". 此时修改线程1 的工作内存中的值, 线程2 的工作内存不一定会及时变化.

4.指令重排序

如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,原来是按1->2->3的方式执行,优化后可能会按 1->3->2的方式执行,也是没问题,可以少跑一次前台。这种叫做指令重排序。
编译器对于指令重排序的前提是 "保持逻辑不发生变化". 这一点在单线程环境下比较容易判断, 但
是在多线程环境下就没那么容易了, 多线程的代码执行复杂程度更高, 编译器很难在编译阶段对代
码的执行效果进行预测, 因此激进的重排序很容易导致优化后的逻辑和之前不等价.

线程安全的代码示例-加锁
public class Demo12 {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);}
}

运行结果

synchronized关键字
synchronized的特性

1)互斥

synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待. 
进入 synchronized 修饰的代码块, 相当于 加锁。
退出 synchronized 修饰的代码块, 相当于 解锁。

synchronized用的锁是存在Java对象头里的。

synchronized 的底层是使用操作系统的 mutex lock 实现的 .

2)可重入

Java 中的 synchronized 可重入锁 ,

synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;

理解死锁

一个线程没有释放锁 , 然后又尝试再次加锁 .
// 第一次加锁, 加锁成功
lock();
// 第二次加锁, 锁已经被占用, 阻塞等待. 
lock();
按照之前对于锁的设定 , 第二次加锁的时候 , 就会阻塞等待 . 直到第一次的锁被释放 , 才能获取到第
二个锁 . 但是释放第一个锁也是由该线程来完成 , 结果这个线程已经躺平了 , 啥都不想干了 , 也就无
法进行解锁操作 . 这时候就会 死锁 .
代码示例
static class Counter {public int count = 0;synchronized void increase() {count++;}synchronized void increase2() {increase();}
}

在上面的代码中, increase 和 increase2 两个方法都加了 synchronized, 此处的 synchronized 都是针对 this 当前对象加锁的. 
在调用 increase2 的时候, 先加了一次锁, 执行到 increase 的时候, 又加了一次锁. (上个锁还没释
放, 相当于连续加两次锁),这个代码是完全没问题的. 因为 synchronized 是可重入锁.

小结

在可重入锁的内部, 包含了 "线程持有者" 和 "计数器" 两个信息. 
如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取到锁, 并让计数器自增. 
解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)

形成死锁的四个必要条件

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

1.互斥条件(锁的基本特性)

   当一个线程持有一把锁之后,另一个线程也想要获取到锁,就要阻塞等待。

2.不可抢占条件(锁的基本特性)

   当锁已经被线程1拿到之后,线程2只能等线程1主动释放,不能强行抢过来。

3.请求与保持条件(代码结构)

   一个线程尝试获取多把锁,已经获取到部分数量的锁,但仍尝试获取其它线程已经占有的锁。

4.循环等待/环路等待(代码结构)

   等待的依赖关系,形成了环。

这四个条件同时满足时,系统中就可能发生死锁。

解决死锁的方法通常包括死锁预防、死锁避免、死锁检测和死锁恢复等策略。 

比如包括调整代码结构,避免循环等待;对锁进行编号,先加编号大的锁或编号小的锁。

synchronized的使用示例

 synchronized 本质上要修改指定对象的 "对象头". 从使用角度来看, synchronized 也势必要搭配一个具体的对象来使用.

1.直接修饰普通方法

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

2.直接修饰静态方法

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

3.修饰代码块

锁当前对象

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

锁类对象

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

我们重点要理解,synchronized 锁的是什么. 两个线程竞争同一把锁, 才会产生阻塞等待. 
两个线程分别尝试获取两把不同的锁, 不会产生竞争.

Java标准库中的线程安全类
Java 标准库中很多都是线程不安全的 . 这些类可能会涉及到多线程修改共享数据 , 又没有任何加锁措施 .
ArrayList
LinkedList
HashMap
TreeMap
HashSet
TreeSet
StringBuilder
但是还有一些是线程安全的 . 使用了一些锁机制来控制 .
Vector ( 不推荐使用 )
HashTable ( 不推荐使用 )
ConcurrentHashMap
StringBuffer

我们可以看到,例如StringBuffer类的成员,有不少是加锁的:

相关文章:

Java之线程篇三

​​​​​​​ 目录 线程状态 观察线程的所有状态 线程状态及其描述 线程状态转换 代码示例1 代码示例2 线程安全 概念 线程不安全的代码示例 线程不安全的原因 线程安全的代码示例-加锁 synchronized关键字 synchronized的特性 小结 形成死锁的四个必要条件 …...

Bootstrap动态设置表格title项

页面searchType <form id"formId"><div class"select-list"><ul><li><select name"searchType" id"searchType"><option value"1">按各节点统计</option><option value"…...

Arrays.sort()方法在Java中的使用:理论与实践

目录 一.概述 二.实现方式 三.具体介绍 1.基本数据类型数组 2.对象数组 1)使对象实现Comparable接口 2)为对象再专门实现一个比较器类 四.进阶技巧 1.基础类型数组实现自定义比较 2.如何进行逆序排序 3.lambda表达式实现比较器类 4.List的排序方法Collection.sort()…...

用AI写论文,千万不要这样用ChatGPT生成参考文献References!!

ChatGPT作为一种先进的语言大模型&#xff0c;被广泛用于生成文本&#xff0c;虽然用ChatGPT辅助论文写作已是大势所趋&#xff0c;但是&#xff0c;用于生成参考文献References的部分还是要谨慎对待。 在学术写作中&#xff0c;参考文献References扮演着至关重要的角色&#…...

Debian 12如何关闭防火墙

在Debian 12中&#xff0c;默认的防火墙管理工具是ufw&#xff08;Uncomplicated Firewall&#xff09;。您可以使用以下命令来关闭防火墙&#xff1a; 关闭防火墙&#xff1a; sudo ufw disable查看防火墙状态&#xff1a; sudo ufw status如果需要重新开启防火墙&#xff1a;…...

windows C++-并行编程-PPL任务并行(二)

延续任务 在异步编程中&#xff0c;一个异步操作在完成时调用另一个操作并将数据传递到其中的情况非常常见。 传统上&#xff0c;这使用回调方法来完成。 在并发运行时中&#xff0c;延续任务提供了同样的功能。 延续任务(也简称为“延续”)是一个异步任务&#xff0c;由另一个…...

快速了解 servlet(SpringMVC 的底层)

Servlet 是 Java EE&#xff08;现 Jakarta EE&#xff09;中用于处理 Web 请求的核心组件。它在 Web 应用程序的服务器端运行&#xff0c;负责接收和处理客户端&#xff08;如浏览器&#xff09;的请求&#xff0c;并生成响应。 尽管现代Web开发更多采用SpringMVC等框架&…...

QT中tr的作用是什么

在Qt框架中&#xff0c;tr() 函数是一个非常重要的宏&#xff0c;它用于国际化和本地化&#xff08;i18n和l10n&#xff09;支持。tr() 函数使得Qt应用程序能够根据不同的语言环境&#xff08;locale&#xff09;显示相应的翻译文本&#xff0c;从而支持多种语言。 具体来说&a…...

OpenCV结构分析与形状描述符(7)计算轮廓的面积的函数contourArea()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算轮廓的面积。 该函数计算轮廓的面积。与 moments 类似&#xff0c;面积是使用格林公式计算的。因此&#xff0c;返回的面积与你使用 drawCo…...

内网环境使用Docker部署Qwen2模型-vLLM篇

在此之前&#xff0c;我们已成功利用Docker与Ollama框架&#xff0c;在内网环境中部署了Qwen2模型。下面我们再来看一下使用Docker与vLLM框架部署Qwen2模型。 准备vLLM镜像 在一台具备网络环境的机器上执行以下命令&#xff0c;拉取vLLM的镜像&#xff1a; # 官方镜像 docke…...

Rust的常数、作用域与所有权

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust到底值不值得学&#xff0c;之一 -CSDN博客 Rust到底值不值得学&#xff0c;之二-CSDN博客 Rust的数据类型-CSDN博客 3.7 常…...

Spring 源码解读:解决循环依赖的三种方式

引言 在复杂的应用开发中&#xff0c;循环依赖是一个常见的问题。简单来说&#xff0c;循环依赖是指两个或多个Bean之间互相依赖&#xff0c;导致程序无法正常实例化这些Bean。Spring容器通过依赖注入&#xff08;DI&#xff09;来管理Bean的创建与生命周期&#xff0c;并在遇…...

Web3 详解

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

Spring 中依赖注入注解的区别详解

一、依赖注入的基本概念 依赖注入是一种设计模式,通过将对象的依赖以参数的形式传入类中,而不是在类中自行创建依赖对象。这样做有几个好处: 降低耦合度:类与类之间的依赖关系变得更清晰,避免了硬编码依赖。提高可测试性:通过依赖注入,可以轻松地进行单元测试,因为可以…...

PTA求一批整数中出现最多的个位数字

作者 徐镜春 单位 浙江大学 给定一批整数&#xff0c;分析每个整数的每一位数字&#xff0c;求出现次数最多的个位数字。例如给定3个整数1234、2345、3456&#xff0c;其中出现最多次数的数字是3和4&#xff0c;均出现了3次。 输入格式&#xff1a; 输入在第1行中给出正整数…...

探索国产编程工具:如何实现工作效率翻倍

在当前软件开发领域&#xff0c;国产编程工具正在迅速发展&#xff0c;它们在功能、性能以及用户体验上都有显著提升&#xff0c;以下是一些国产编程工具&#xff0c;它们可以帮助开发者提升工作效率。 智能代码编辑器 CodeGeeX&#xff1a;这是一款由清华大学和智谱AI合作开…...

秒懂:进程相关的操作

1.进程的查看 1.1创建test.cc文件&#xff0c;运行以下代码 #include <stdio.h> #include <sys/types.h> #include <unistd.h>int main() {while(1){sleep(1);} return 0;}1.2 执行以下命令 1. 运行test.cc文件 并将其最终的可执行文件命名为 test gcc t…...

PDF 软件如何帮助您编辑、转换和保护文件。

如何找到最好的 PDF 编辑器。 无论您是在为您的企业寻找更高效的 PDF 解决方案&#xff0c;还是尝试组织和编辑主文档&#xff0c;PDF 编辑器都可以在一个地方提供您需要的所有工具。市面上有很多 PDF 编辑器 — 在决定哪个最适合您时&#xff0c;请考虑这些因素。 1. 确定您的…...

蓝桥杯嵌入式国三备赛经验分享

1 学习STM32入门视频 向大家推荐一套宝藏级别的视频&#xff1a;【STM32入门教程-2023版 细致讲解 中文字幕】 如果已经比过蓝桥杯单片机或学习过单片机相关课程的同学&#xff0c;你们可以尝试不需要STM32套件进行学习。如果没有学过单片机相关课程的同学&#xff0c;可以买…...

AI编程工具合集

1. 简介 1.1. 概述 AI编程,即人工智能编程,是编写用于创建智能系统(如机器学习模型、自然语言处理应用程序等)的代码的过程。AI编程涉及使用算法和数据结构来实现能够执行任务的程序,这些任务通常需要人类智能才能完成。 AI编程的基础是计算机科学原理,包括数据结构、…...

[网络编程]通过java用TCP实现网络编程

文章目录 一. 通过java用TCP实现网络编程api介绍代码实现上述代码存在的问题 一. 通过java用TCP实现网络编程 api介绍 1. ServerSocket ServerSocket是专门给服务器用的api 构造方法: 方法: 2. Socket 不管是客⼾端还是服务端Socket&#xff0c;都是双⽅建⽴连接以后&#…...

Python(TensorFlow)和Java及C++受激发射损耗导图

&#x1f3af;要点 神经网络监督去噪预测算法聚焦荧光团和检测模拟平台伪影消除算法性能优化方法自动化多尺度囊泡动力学成像生物研究多维分析统计物距粒子概率算法 Python和MATLAB图像降噪算法 消除噪声的一种方法是将原始图像与表示低通滤波器或平滑操作的掩模进行卷积。…...

IEEE投稿模板翻译

>将这一行替换为您的稿件id号(双击此处编辑)< IEEE 期刊和会议论文的撰写准备&#xff08;2022&#xff09; 第一作者 A. 作者&#xff0c;IEEE成员&#xff0c;第二作者 B. 作者&#xff0c;第三作者 C. 作者 Jr.&#xff0c;IEEE成员 摘要—本文档为IEEE会刊、期刊和…...

log4j 1.x 日志输出线程以唯一ID的形式配置

在 Log4j 1.x 中&#xff0c;直接以线程ID&#xff08;如Java中的Thread.currentThread().getId()返回的ID&#xff09;的形式记录日志是可行的&#xff0c;但 Log4j 1.x 本身并不直接提供一个内建的、自动将每个线程ID转换为“同一时间段内唯一ID”的机制。线程ID本身在JVM的上…...

宏观学习笔记:GDP分析(二)

GDP分析&#xff08;一&#xff09;主要是介绍GDP相关的定义以及核算逻辑&#xff0c;本节主要介绍GDP的分析思路。GDP分析主要是2种方法&#xff1a;总量分析和结构分析。 1. 总量分析 1.1 数值选择 一般情况下&#xff0c;分析的对象都是 官方公布的GDP当季值。 1.2 趋势规…...

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位&#xff1a;为1时表示在内存期间被访问过&#xff0c;为0时表示未被访问&#xff1b;修改位&#xff1a;为1时表示该页面自从被装入内存后被修改过&#xff0c;为0时表示未修改过。 置换页面时&#xff0c;最先置换访问位和修改位为…...

C高级编程 第十六天(树 二叉树)

1.树 1.1结构特点 非线性结构&#xff0c;有一个直接前驱&#xff0c;但可能有多个直接后继有递归性&#xff0c;树中还有树可以为空&#xff0c;即节点个数为零 1.2相关术语 根&#xff1a;即根结点&#xff0c;没有前驱叶子&#xff1a;即终端结点&#xff0c;没有后继森…...

OpenCV结构分析与形状描述符(11)椭圆拟合函数fitEllipse()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 围绕一组2D点拟合一个椭圆。 该函数计算出一个椭圆&#xff0c;该椭圆在最小二乘意义上最好地拟合一组2D点。它返回一个内切椭圆的旋转矩形。使…...

904.水果成篮

题目 链接&#xff1a;leetcode链接 思路分析&#xff08;滑动窗口&#xff09; 读完题目&#xff0c;很明显&#xff0c;这个题目需要我们寻找一个最长子数组&#xff0c;使得这个子数组里面最多存在两种不同的数字&#xff0c;很容易联想到使用滑动窗口。 另外&#xff…...

【网络安全】漏洞挖掘之 2FA 恢复代码安全措施不当

未经许可,不得转载。 文章目录 正文正文 目标:example.com 2024年6月,我在HackerOne上参与一个私人项目时发现了一个与2FA(双因素身份验证)恢复代码管理相关的安全漏洞。该漏洞发生在用户禁用并重新启用2FA的过程中。问题在于,系统在2FA重新启用后,仍然接受此前生成的…...