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

Java--多线程基础知识(2)

一.多线程的中断

1.通过自定义的变量来作为标志位

import java.util.Scanner;public class Demo1 {public static boolean flg = false;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{while (!flg){System.out.println("t1线程正在执行");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("t1线程被中断");}}System.out.println("t1线程停止");});t1.start();Scanner scanner = new Scanner(System.in);scanner.next();flg = true;}
}

在代码中,我们将 flg 作为标志位,当代码运行的时候我们需要输入任意键为就可以改变 flg,以此来达到停止代码运行的作用;

注意:flg 必须作为全局变量,因为 lambda 捕获的局部变量是不能被修改的,这是为了确保代码的安全性和一致性,由于我们要对flg 进行修改,所以我们要将 flg 作为全局变量。

2.使用 Thread.interrupted() 或者

Thread.currentThread.isInterrupted()代替标志位

import java.util.Scanner;public class Demo2 {public static void main(String[] args) {Thread t1 = new Thread(()->{Thread cur = Thread.currentThread();while (!cur.isInterrupted()){System.out.println("t1线程正在执行");try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("t1线程被中断");}}System.out.println("t1线程停止");});t1.start();Scanner scanner = new Scanner(System.in);scanner.next();t1.interrupt();}
}

以上代码我们将 cur.isInterrupted 作为标志位,当我们输入任意字符之后就会调用 t1.interrupt() 方法,将标志位改为 false,但是运行后并且输入后我们发现代码仍在执行,

Java的线程中断机制有这样的设计:

当一个线程在阻塞状态(Thread.join,Thread.sleep,Thread.wait)下被中断时,中断标志位会被自动清除,也就是说当调用 Thread.interrupt() 之后,虽然  !cur.isInterrupted() 改为了 false,但是由于被清除了标志位,又变为了 true,这才导致程序仍然在运行。

也就是说 interrupt 方法做了两件事:首先将标志位设置为了 true,但是由于唤醒了 sleep ,以异常的方式返回了,清除了线程的标志位,将标志位重新设置为了 false。针对这种情况,我们需要在 catch 中做更详细的处理。

二.线程的等待与安全问题

public class Demo3 {static int result = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {result++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {result++;}});t1.start();t2.start();Thread.sleep(1000);System.out.println( result);}
}

以上代码我们创建了两个线程 : 分别是 t1,t2,这两个线程同时对 result ++,正常情况下,result 的结果肯定是100000的,但在我们运行代码后:

我们发现并没有到达100000,这就是多线程引发的线程安全问题,线程对 result++ 要进行三个操作:首先将 result 从主内存中读取到工作内存中,其次将 result 在工作内存中++,最后将result的值放回到主内存中,问题就出在这几步上,由于操作不是原子的,导致在 t1 从主内存中读取到 result的值并且++之后,t2也进行了从主内存中读取到了 result 的值,此时t1 和t2都需要将工作内存中的值写入到主内存当中,这样就会导致其中一个线程的值会覆盖掉另一个线程的值,这才导致了 result 的值并没有到达100000;

针对这个问题:

我们可以让某一个线程通过 Thread.sleep() 来等待另一个线程运行,但这种方法我们无法确定另一个线程运行多久,所以不推荐使用。

第二个方法是运行 join() 方法,

public class Demo3 {static int result = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {result++;}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {result++;}});t1.start();t1.join();t2.start();Thread.sleep(1000);System.out.println( result);}
}

在 t2 运行之前调用 t1.join() 方法,此时main线程会等待 t1 线程运行完成之后再往下走调用 t2.start,此时就解决了上述问题。

三.线程的状态

线程的状态分为六种:

new,runnable,terminated 状态演示:

public class Demo4 {public static void main(String[] args) {Thread t1 = new Thread(()->{for (int i = 0; i < 100; i++) {}});System.out.println(t1.getState());t1.start();System.out.println(t1.getState());try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(t1.getState());}}

timed_waiting , blocked 状态演示:

public class Demo5{static Object object = new Object();public static void main(String[] args) {Thread t1 = new Thread(()->{synchronized( object){try {Thread.sleep(10000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 = new Thread(()->{synchronized ( object){while ( true){}}});t1.start();t2.start();try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(t1.getState());System.out.println(t2.getState());}
}

将上述部分代码进行更改,就可以得到 waiting 状态:

        Thread t1 = new Thread(()->{synchronized( object){try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}});

四.线程的锁

还是对上述的线程安全问题进行解决,线程安全问题其中的导火索之一便是代码的非原子性,由于一件事情要分几步来操作,这种情况容易出问题,所以就出现了锁,锁可以将几个步骤的操作绑定在一起,这样就可以变相的实现代码的原子化。

public class Demo6 {static int result = 0;static Object locker = new Object();public static void add(int a){synchronized (locker){result++;}}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {add(result);}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {add(result);}});t1.start();t2.start();Thread.sleep(100);System.out.println( result);}
}

sychronized(Object object){ }

其中 sychronized 就是锁的关键字,他需要的参数是 Object 类,就像是对 object 上了锁,其他人想要进是没有办法的,除非你释放了锁,当你走完{ }的时候,此时就完成了对锁的释放。

上述代码我们对 add类里面的内容上了锁,我们还可以对线程的内容直接加锁:

public class Demo6 {static int result = 0;static Object locker = new Object();public static void add(int a){result++;}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{synchronized(locker){for (int i = 0; i < 50000; i++) {add(result);}}});Thread t2 = new Thread(()->{synchronized(locker){for (int i = 0; i < 50000; i++) {add(result);}};});t1.start();t2.start();Thread.sleep(100);System.out.println( result);}
}

运行结果不变;

要注意:我们不能只对一个线程加锁,那样的话是没有意义的:

public class Demo6 {static int result = 0;static Object locker = new Object();public static void add(int a){result++;}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{synchronized(locker){for (int i = 0; i < 50000; i++) {add(result);}}});Thread t2 = new Thread(()->{for (int i = 0; i < 50000; i++) {add(result);};});t1.start();t2.start();Thread.sleep(100);System.out.println( result);}
}

这是因为如果我们只对一个线程的内容上锁了,另一个线程没有对同一个Object类上锁,两个线程还是会各自运行各自的。

最后就是:两个线程针对不同的 Object 类上锁也会有线程安全问题:

public class Demo6 {static int result = 0;static Object locker = new Object();static Object locker2 = new Object();public static void add(int a){result++;}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{synchronized(locker){for (int i = 0; i < 50000; i++) {add(result);}}});Thread t2 = new Thread(()->{synchronized(locker2){for (int i = 0; i < 50000; i++) {add(result);};}});t1.start();t2.start();Thread.sleep(100);System.out.println( result);}
}


文章转载自:

http://JXC7XUcy.cLpdm.cn
http://gc1zQPA7.cLpdm.cn
http://JdK0RQJD.cLpdm.cn
http://t9I3cJj4.cLpdm.cn
http://IA0T8a2Q.cLpdm.cn
http://RjdqGMeA.cLpdm.cn
http://iSqBpw0S.cLpdm.cn
http://KusWxbX5.cLpdm.cn
http://qOBLf2Le.cLpdm.cn
http://zTC4avWG.cLpdm.cn
http://Sa8g7PpS.cLpdm.cn
http://kg2mmqt4.cLpdm.cn
http://xG5M1s5k.cLpdm.cn
http://czO0Rtzl.cLpdm.cn
http://GEzvOGz1.cLpdm.cn
http://qgRhHRSE.cLpdm.cn
http://yHFE4cfV.cLpdm.cn
http://SfWboYFz.cLpdm.cn
http://qRENcrON.cLpdm.cn
http://DWdjhJDa.cLpdm.cn
http://jiTd2Crl.cLpdm.cn
http://bK1PjNJg.cLpdm.cn
http://KkKcA0bB.cLpdm.cn
http://bzrytfXW.cLpdm.cn
http://39hQKUcl.cLpdm.cn
http://DeujXarp.cLpdm.cn
http://qIftwUgG.cLpdm.cn
http://8WLWumwc.cLpdm.cn
http://paBvPEGt.cLpdm.cn
http://WaOOBxZD.cLpdm.cn
http://www.dtcms.com/a/385651.html

相关文章:

  • 活泼解析pthread_join函数:多线程世界的等待仪式
  • 机器视觉的智能手表后盖激光打标应用
  • 第七章 来日方长(2025.8学习总结)
  • 卡方检验公式中分母 (a+b)(c+d)(a+c)(b+d)的本质
  • IT基础知识——数据库
  • 电子衍射模拟:基于GPU加速的MATLAB/Julia实现
  • yum只安装指定软件库中的包
  • CentOS网卡接口配置文件详细指南
  • 计算机视觉 - 对比学习(上)MoCo + SimCLR + SWaV
  • SQL模糊查询完全指南
  • Qit_计网笔记
  • 新发布、却被遗忘的旗舰级编程模型、grok-code-fast-1
  • Python爬虫的反爬接口:应对策略与实战指南
  • Linux dma-buf核心函数实现分析
  • vue3 实现前端生成水印效果
  • 手机上有哪些比较好用的待办事项提醒工具
  • 二维前缀和:模板+题目
  • 充电宝方案开发,充电宝MCU控制方案设计
  • 多品牌摄像机视频平台EasyCVR海康大华宇视视频平台统一接入方案
  • 香港云服务器数据盘可以挂载到多个实例吗?
  • 【C语言】用程序求1!+2!+3!+4!+...n!的和,来看看?
  • 【C++】浅谈智能指针
  • 第三章 神经网络入门笔记:从概念到实践全解析
  • 20250915在荣品RD-RK3588-MID开发板的Android13系统下使用TF卡刷机
  • 四元论的正确性数学原理
  • 你的第一个AI项目部署:用Flask快速搭建模型推理API
  • MyBatis-相关知识点
  • 【Nginx开荒攻略】Nginx配置文件语法规则:从基础语法到高级避坑指南
  • 【系统分析师】2024年下半年真题:论文及解题思路
  • Linux 标准输入 标准输出 标准错误