【JavaEE】多线程(三)线程的状态
创始人
2024-04-29 10:27:22
0

✨哈喽,进来的小伙伴们,你们好耶!✨

🛰️🛰️系列专栏:【JavaEE】

✈️✈️本篇内容:线程的状态,线程安全问题!

🚀🚀代码存放仓库gitee:JavaEE初阶代码存放!

⛵⛵作者简介:一名双非本科大三在读的科班Java编程小白,道阻且长,星夜启程!

 接着上篇博客,我们已经学习了Thread类的基本用法,了解到创建线程,中断线程,等待线程,休眠线程是如何操作的,那么今天呢,我们接着往下学习。

我们知道,在之前博客已经介绍到进程有它的状态,比如就绪和阻塞,这里的状态决定了系统按照啥样的方式来调度这个进程。但是只针对一个进程里面只有一个线程的情况,那么更多的情况是一个进程中是存在多个线程的,所谓的状态其实是绑定在线程上的

目录

1、线程的状态

一、NEW

二、 TERMINATED

 三、RUNNABLE

四、TIMED_WAITING

五、 BLOCKED

六、WAITING

 2、线程安全问题

1、线程不安全的原因

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

3、加锁操作

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


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();}
}

二、 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());}
}

 三、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());}
}

四、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());}
}

五、 BLOCKED

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

六、WAITING

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

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

 2、线程安全问题

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

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 也能够禁止指令从排序!!

相关内容

热门资讯

安卓系统可以去水印吗,轻松恢复... 你有没有遇到过这种情况:手机里下载了好多好看的视频,结果一看,哎哟,全是水印!心里那个不舒服啊,是不...
安卓系统平板看论文,安卓平板论... 你有没有想过,在安卓系统平板上阅读论文竟然可以这么酷炫?想象你手捧着一款轻薄的平板,在阳光明媚的午后...
安卓能刷pe系统,一键实现系统... 你有没有想过,你的安卓手机是不是也能来个“变身大法”,从普通模式升级到超级模式呢?没错,今天就要来聊...
安卓系统的运动数据在哪,运动数... 你有没有发现,手机里的安卓系统里藏着不少秘密呢?比如,你每天的运动数据,它们都藏在哪个角落里呢?别急...
系统miui是不是安卓系统软件... 你有没有想过,你的手机里那个熟悉的MIUI系统,它到底是不是安卓系统的一部分呢?这可是个有趣的问题,...
安卓修改系统版本骗软件,软件骗... 你知道吗?在安卓系统世界里,有时候一些小改动就能掀起大波澜。今天,就让我带你一探究竟,揭秘那些通过修...
安卓平板如何刷凤凰系统,凤凰系... 亲爱的平板用户,你是否厌倦了安卓系统的千篇一律?想要给你的平板来个焕然一新的变身?那就跟着我一起,探...
安卓手机哪款系统好,安卓手机系... 你有没有想过,你的安卓手机系统到底怎么样?是不是有时候觉得卡顿,有时候又觉得功能不够强大?别急,今天...
安卓系统qq炫舞怎么换系统,轻... 亲爱的安卓用户们,你是不是也和我一样,对QQ炫舞这款游戏爱得深沉呢?但是,有时候,我们可能会觉得系统...
安卓原生系统图案忘了,图案解锁... 亲爱的手机控们,你是否也有过这样的经历:手机屏幕上那些熟悉的安卓原生系统图案,突然间就消失得无影无踪...
安卓苹果系统版本列表,安卓与i... 你有没有发现,手机更新换代的速度简直就像坐上了火箭呢?从安卓到苹果,每个系统版本的更新都像是一场科技...
在安卓系统和网关通信,安卓系统... 在安卓系统中,网关通信是如何工作的?在当今数字化的世界里,安卓系统已经成为了智能手机和平板电脑的主流...
恢复删除的短信安卓系统,轻松找... 手机里的短信,有时候就像生活中的小确幸,记录着我们的喜怒哀乐。但你知道吗?有时候,一条重要的短信不小...
bemyeyes安卓系统,功能... 你有没有想过,如果有一款手机系统,它不仅能让你轻松管理日常事务,还能让你的手机瞬间变身成为你的私人助...
汽车怎么下载安卓系统,如何下载... 你有没有想过,你的爱车也能装上安卓系统,变成一个智能移动中心呢?没错,现在汽车界也开始流行“跨界”了...
安卓系统软件编写,功能与特性的... 你有没有想过,手机里的那些神奇应用是怎么诞生的呢?没错,就是安卓系统软件编写这个神秘的过程。今天,就...
安卓系统微信总是延迟,具体操作... 你是不是也遇到了这样的烦恼?每次打开微信,总是慢吞吞的,让人等得心焦火燎。没错,说的就是你,安卓系统...
安卓系统格式化指令,轻松掌握数... 手机里的安卓系统突然出了点小状况,是不是让你有点头疼呢?别急,今天就来给你详细说说安卓系统格式化指令...
电脑安卓系统卡嘛,安卓系统卡顿... 你有没有遇到过这种情况:手机用得正欢,突然间,安卓系统就像老牛拉车一样慢吞吞的,让人抓狂!电脑安卓系...
华为荣耀的安卓系统精简,极致体... 你有没有发现,现在的手机越来越像是一个小型的电脑了?各种功能齐全,操作复杂,有时候用起来还真是让人头...