JavaEE——多线程的状态及线程安全问题

目录

一、线程的状态

1、NEW

2、 TERMINATED

3、RUNNABLE

4、TIMED_WAITING

5、 BLOCKED

6、WAITING

二、线程安全问题

1、线程不安全的原因

2、一个线程不安全的实例

3、加锁操作

4、产生线程不安全的原因

什么是内存可见性呢?

解决方案?

5、指令重排序


Java 线程共有几种状态?状态之间怎么切换的?
NEW: 安排了工作 , 还未开始行动 . 新创建的线程 , 还没有调用 start 方法时处在这个状态 .
RUNNABLE: 可工作的 . 又可以分成正在工作中和即将开始工作 . 调用 start 方法之后 , 并正在
CPU 上运行 / 在即将准备运行 的状态 .
BLOCKED: 使用 synchronized 的时候 , 如果锁被其他线程占用 , 就会阻塞等待 , 从而进入该状
.
WAITING: 调用 wait 方法会进入该状态 .
TIMED_WAITING: 调用 sleep 方法或者 wait( 超时时间 ) 会进入该状态 .
TERMINATED: 工作完成了 . 当线程 run 方法执行完毕后 , 会处于这个状态 .

一、线程的状态

1、NEW

new表示安排了工作还未开始行动。

看代码:while()循环里面啥也不用给,我们直接通过t.getState()这个方法获取指定线程的状态

public class demo14 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (true) {}});System.out.println(t.getState());//通过这个方法获取指定线程的状态t.start();}
}

2、 TERMINATED

TERMINATED:表示工作完成了。

代码:这里我们直接去掉while()循环,啥也不用干,那肯定代码是执行完了,先调用start方法,然后获取我们的线程状态。

public class demo14 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {});t.start();Thread.sleep(1000);System.out.println(t.getState());}
}

3、RUNNABLE

RUNNABLE:表示就绪状态。处于这个状态的线程,就是在就绪队列中,随叫随到,随时可以到CPU上执行。

代码:直接给一个空的while循环,不用加sleep等操作,方便我们观察代码的就绪状态。

public class demo14 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (true) {}});t.start();Thread.sleep(1000);System.out.println(t.getState());}
}

4、TIMED_WAITING

即代码中调用了sleep就会进入到TIMED_WAITING,意思就是当前线程在一定时间内是阻塞状态。

public class demo14 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();Thread.sleep(1000);System.out.println(t.getState());}
}

5、 BLOCKED

表示排队等待其他事情。详情参考本文下面的3、加锁操作。

6、WAITING

也表示排队等待其他事情。

OK,到这里就可以总结出线程状态转换的简易图。


二、线程安全问题

谈到线程安全,那么这里是很重要的一个板块,在面试中如果遇到多线程的问题,基本都会涉及到线程安全问题,同时也是一个难点,希望大家认真阅读。

1、线程不安全的原因

操作系统调度线程的时候是随机的,线程之间是抢占式执行,正因为这种特性很可能会导致程序出现一些bug。

2、一个线程不安全的实例

这里我们使用两个线程,对同一个整型变量各自-自增5w次,看最终的结果。

class Counter{public int count = 0;public void increase(){count++;}public class demo15 {private static  Counter counter = new Counter();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() ->{for (int i = 0; i < 50000; i++) {counter.increase();}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {counter.increase();}});t1.start();t2.start();t1.join();t2.join();//必须要在t1,t2都执行完了之后,再打印count//否则,t1,t2,main都是并发执行的关系,导致t1,t2还没执行完,就先执行了下面的打印操作//在main中打印两个线程执行自增完成之后,得到的count结果System.out.println(counter.count);}
}

注意这里我们需要等待线程t1,t2都结束之后再打印结果,因为main线程和t1,t2线程是并发执行的关系,所以使用到了join函数

我们发现结果并不是10w,而是一个介于5w - 10w之间的数字,那么为什么会这样呢?

要想知道其中原因,我们就要来了解一个count++到低是如何实现的。

站在CPU的角度:count++实际上是三个CPU指令。

1、把内存中的count的值,加载到CPU寄存器中。(load)

2、把寄存器中的值+1。(add)

3、把寄存器的值写回到内存的count中。(save)

由于线程调度是抢占式执行的,并且这个count++操作是分为3步来完成的,那么t1,t2同时执行这三个操作的时候,顺序上是有很多种可能的,这就可能导致我们最终的结果达不到10w。

结果为什么在5w - 10w之间?

 这5w并发相加中,有时候可能是串行的(+2),有时候可能是交错的,串行与交错多少次咋们不知道,这都是随机的,极端情况下,所有操作都是串行的,结果就是10w,所有操作都是交错的,结果就是5w,不过都是小概率事件。

3、加锁操作


那么如何解决上述线程不安全问题呢?

答案:加锁!

这里举个例子:比如李华准备去ATM机里面取钱,那么ATM机器一般是在一个小房间里面,当李华走进去的时候他就可以给这个房间的门上锁,这样赵四,王五等等想取钱的话只能等李华取完(这个等待操作就是阻塞),李华取完钱后便可以给这个房间解锁,下一个人才能进去取钱。

如何加锁?

那么java中加锁的方式有很多种,最常使用的是 synchronized 关键字。我们可以给上述代码重大的自增函数increase()前面加上synchronized 关键字就是加锁成功了。

 public int count = 0;synchronized public void increase(){count++;}

我们在看运行结果,这下便是10w,符合我们的预期。

那么给方法加上 synchronized 关键字之后,此时进入方法就会自动加锁,离开方法就会自动锁,当一个线程加锁成功的时候,其他线程尝试加锁就会触发阻塞等待。这个时候线程的状态就称为BLOCKED。

4、产生线程不安全的原因

1、线程是抢占式执行的,线程间的调度充满随机性。(线程不安全的根本原因)

2、多个线程对同一个变量进行修改操作。

3、针对变量的操作不是原子的,通过加锁操作就是把几个指令打包成一个原子的。

4、内存可见性。

什么是内存可见性呢?

假设我们这里有两个线程t1,t2;t1只进行读操作,t2在合适的时候会进行修改操作。

t1 这个线程在循环读这个变量。那么读取内存操作,相比于读取寄存器,是一个非常低效的操作作。 (慢 3-4 个数量级)
因此在 t1 中频繁的读取这里内存的值就会非常低效。
而且如果 t2 线程迟迟不修改,t1 线程读到的值又始终是一样的值!!
因此,t1 可能会 直接从寄存器里读(即不执行 load )一旦 t1 做出了这种大胆的假设,此时万一 t2 修改了 count 值, t1 就不能感知到了。

import java.util.Scanner;public class demo16 {public static int isquit = 0;public static void main(String[] args) {Thread t = new Thread(()-> {while (isquit == 0){}System.out.println("循环结束,t线程退出!");});t.start();Scanner sc = new Scanner(System.in);System.out.println("请输入一个isquit的值:");isquit = sc.nextInt();System.out.println("main线程执行完毕!");}
}

 

那么这个案例就很好的说明了上述内存可见性的问题,我们手动输入isquit的值为2,那么while循环结束按理说应该打印 System.out.println("循环结束,t线程退出!");正是因为t线程一直反复读isquit的值,于是它直接从寄存器读,那么我们修改了isquit的值,它也就感知不到了。所以就没有打印 循环结束,t线程退出! 这条语句。

解决方案?

1、使用synchronized 关键字。synchronized 不光能够保证原子性,同时也能够保证内存可见性。被synchronized 包裹起来的代码,编译器就不敢轻易做出上述假设,就相当于手动禁止了编译器的优化。

2、使用volatile关键字。volatile和原子性无关,但是能够保证内存可见性。使得编译器每次都重新从内存中读取isquit的值。

  public static volatile int isquit = 0;

5、指令重排序

这个操作也是编译器优化的一种操作,编译器会智能的调整代码的前后顺序从而提高程序的效率。

保证逻辑不变的前提,再去调整顺序。使用synchronized 也能够禁止指令从排序!!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

精密制造与质量控制:保障滚珠丝杆重载运行精度

滚珠丝杆作为精密机械传动领域的重要零部件&#xff0c;能够将旋转动力精准地转化为流畅的直线运动。在数控机床、精密制造及高度自动化生产线上扮演着不可或缺的角色。在应对温度波动、负载突变及严苛环境条件的考验中&#xff0c;都有很好的表现。那么&#xff0c;应该如何确…

【多线程】面试高频考点!JUC常见类的详细总结,建议收藏!

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;多线程 / javaEE初阶 JUC是“Java Util Concurrency”的缩写&#xff0c;指的是Java并发工具包&#xff0c;它位于java.util.concurrent包及其子包中。JUC包提供了大量用于构建并发应用程序的工具和…

深入解析Python 中的 sortedcontainers 库:高效的排序数据结构

在日常的 Python 编程中&#xff0c;列表&#xff08;list&#xff09;、集合&#xff08;set&#xff09;和字典&#xff08;dict&#xff09;是常用的数据结构。然而&#xff0c;在某些特定的场景下&#xff0c;我们需要对数据进行排序&#xff0c;并且希望在插入、删除或访问…

计算机网络32——Linux-文件io-2文件系统

1、阻塞和非阻塞 想要将文件以非阻塞方式打开&#xff0c;有两种方式 &#xff08;1&#xff09;需要将文件关闭&#xff0c;再用非阻塞方式打开 &#xff08;2&#xff09;fctnl函数&#xff0c;先获取旧属性&#xff0c;再添加一个新属性 阻塞函数 阻塞函数一直在等待输入…

从更底层的角度理解网站的访问过程

文章目录 1.示例&#xff0c;访问www.baidu.com是如何返回数据的1.输入www.baidu.com回车2.检查本机的C:\Windows\System32\drivers\etc\hosts配置文件夹下有没有这个域名对应的映射&#xff1a; 1.示例&#xff0c;访问www.baidu.com是如何返回数据的 1.输入www.baidu.com回车…

开源数据集网站合集

一.Google数据集 链接&#xff1a;https://datasetsearch.research.google.com/ 二.Huggingface数据集 链接1&#xff1a;GitHub - huggingface/datasets: &#x1f917; The largest hub of ready-to-use datasets for ML models with fast, easy-to-use and efficient dat…

深入解析:HTTP 和 HTTPS 的区别

网络安全问题正变得日益重要&#xff0c;而 HTTP 与 HTTPS 对用户数据的保护十分关键。本文将深入探讨这两种协议的特点、工作原理&#xff0c;以及保证数据安全的 HTTPS 为何变得至关重要。 认识 HTTP 与 HTTPS HTTP 的工作原理 HTTP&#xff0c;全称超文本传输协议&#xf…

2-103 基于matlab的光电信号下血氧饱和度计算

基于matlab的光电信号下血氧饱和度计算&#xff0c;光转换成电信号时&#xff0c;由于动脉对光的吸收有变化而其他组织对光的吸收基本不变&#xff0c;得到的信号就可以分为直流DC信号和交流AC信号。提取AC信号&#xff0c;就能反应出血液流动的特点。这种技术叫做光电容积脉搏…

如何查看线程

1、首先找到我们的电脑安装jdk的位置&#xff0c;这里给大家展示一下博主本人的电脑jdk路径下的jconsole位置。 2、 ok&#xff0c;那么找到这个jconsole程序我们直接双击打开就可以查看我们电脑的本地进程&#xff1a; jconsole 这里能够罗列出你系统上的 java 进程&#xff0…

[Linux] Linux操作系统 进程的状态

标题&#xff1a;[Linux] Linux操作系统 进程的状态 个人主页&#xff1a;水墨不写bug &#xff08;图片来源于网络&#xff09; 目录 一、前置概念的理解 1.并行和并发 2.时间片 3.进程间具有独立性 4.等待的本质 正文开始&#xff1a; 在校的时候&#xff0c;你一定学过《…

java 框架组件

Java 框架是一系列预先编写好的、可复用的软件组件&#xff0c;它们旨在帮助开发者快速构建高质量的应用程序。Java 社区拥有众多优秀的框架&#xff0c;涵盖了从 Web 开发到大数据处理的各个领域。下面是一些流行的 Java 框架及其主要用途&#xff1a; Spring框架&#xff1a;…

新手教学系列——非正常关机导致MySQL权限表(db)损坏及修复详解

在使用MySQL的过程中,我们常常会遇到一些问题,尤其是当服务器或主机非正常关机或重启时,MySQL的某些表,特别是权限表(如 mysql.db 表),可能会损坏,导致数据库无法启动或访问。这种情况对生产环境的数据库系统来说是相当严重的,因此掌握修复方法非常重要。 本篇文章将…

【STM32系统】基于STM32设计的DAC输出电压与ADC检测电压系统(简易万用表,检测电压电流)——文末工程资料下载

基于STM32设计的DAC输出电压与ADC检测电压系统(简易万用表,检测电压电流) 演示视频: 基于STM32设计的DAC输出电压与ADC检测电压系统(简易万用表,检测电压电流) 前言:本项目实现对STM32的DAC和ADC的程序设计与硬件电路连接实现STM32内部DAC输出电压,并且ADC可以采集电压…

下载2001年版英特尔开发手册与使用网易有道词典

本专栏的任务&#xff0c;是翻译2001年版英特尔开发手册的第3卷。上一节&#xff0c;我写了开篇语。本节&#xff0c;我是打算将这个版本的英特尔开发手册的下载方式公布出来。使得大家可以将其下载回去。如果你看的块的话&#xff0c;你可以自行翻译与学习。 一. 下载英特…

macOS设置 Redis自启动

macOS自定义开机启动程序 1、打开 自动操作app里面的应用程序 过程资料 1、https://juejin.cn/post/7123098435254747149 2、https://blog.twofei.com/889/ 2、编写脚本&#xff0c;可以点击右上角运行测试&#xff0c;保存为 app https://juejin.cn/post/7123098435254747149…

招联金融2025校招内推喇

【投递方式】 直接扫下方二维码&#xff0c;或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus&#xff0c;使用内推码 igcefb 投递&#xff09; 【招聘岗位】 深圳&#xff0c;武汉&#xff1a; 后台开发 前端开发 数据开发 数据运营…

在MySQL中,要查询所有用户及其权限,您可以使用以下命令:

文章目录 1、查询所有用户1.1、登录数据库1.2、select user,host from mysql.user; 2、查看用户的权限 1、查询所有用户 1.1、登录数据库 [rootlocalhost ~]# docker exec -it spzx-mysql /bin/bash rootab66508d9441:/# mysql -uroot -p123456 mysql: [Warning] Using a pas…

什么是电商云手机?可以用来干什么?

随着电商行业的迅速发展&#xff0c;云手机作为一种创新工具正逐渐进入出海电商领域。专为外贸市场量身定制的出海电商云手机&#xff0c;已经成为许多外贸企业和出海电商卖家的必备。本文将详细介绍电商云手机是什么以及可以用来做什么。 与国内云手机偏向于游戏场景不同&…

Elasticsearch导出导入数据

1.概念回顾 2.基础操作 展示详细信息 GET&#xff1a;http://127.0.0.1:9200/_cat/indices?v Java代码将文件导入到ES package com.Graph.medicalgraph;import org.apache.http.HttpHost; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.act…

Linux 下安装mysql

1.检查之前是否安装过mysql rpm -qa | grep mysql 如果之前安装过&#xff0c;删除之前的安装包 rpm -e 安装包 如果没有&#xff0c;进行后续安装 2. 下载 MySQL :: Download MySQL Community Server (Archived Versions)https://downloads.mysql.com/archives/community/ 3…