一个Java线程的线生(线生 vs 人生)

news2024/9/25 17:13:24

java线程的使用

1. Java多线程概述

下面我们看下Java的多线程

1.1 java天生就是多线程的

一个Java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上Java程序天生就是多线程程序,因为执行main()方法的是一个名称为main的线程。

1.1.1 代码案例

执行下面的代码

 
package chapter01;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
public class ThreadDemo {
/**
* 打印出java中所有的线程
* @param args
*/
public static void main(String[] args) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo info : threadInfos) {
System.out.println("[" + info.getThreadId() + "]" + info.getThreadName());
}
System.out.println(Thread.activeCount());
}
}

执行后我们会发现打印了如下的线程信息,说明Java本身就是多线程的

  • [6] Monitor Ctrl-Break //监控Ctrl-Break中断信号的

  • [5] Attach Listener //内存dump,线程dump,类信息统计,获取系统属性等

  • [4] Signal Dispatcher // 分发处理发送给JVM信号的线程

  • [3] Finalizer // 调用对象finalize方法的线程

  • [2] Reference Handler//清除Reference的线程

  • [1] main //main线程,用户程序入口

1.2 线程的生命周期

Thread类提供了六种状态

1.2.1 新建状态(NEW)

当线程对象对创建后,即进入了新建状态,如:Thread thread1 = new MyThread();

1.2.2 运行状态(RUNNABLE)

Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。

​ 线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

1.2.3 阻塞状态(BLOCKED)

​ 处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态

1.2.4 等待状态(WAITING)

​ 进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)

1.2.5 超时等待(TIMED_WAITING)

​ 该状态不同于WAITING,它可以在指定的时间后自行返回。

1.2.6 终止状态(TERMINATED)

​ 线程执行完了或者因异常退出了run()方法,该线程结束生命周

2. 线程的创建方式

创建线程的方式有两种

2.1 继承Thread类

我们可以通过继承Thread类来使用Java的多线程

 
package chapter01.create;
/**
* 创建一个线程并运行
*/
public class threadTest extends Thread {
@Override
public void run() {
System.out.println("线程运行");
}
public static void main(String[] args) {
threadTest threadCreate = new threadTest();
threadCreate.start();
}
}

2.2 实现 Runnable 接口

实现Runnable 接口并交给Thread进行运行

 
package chapter01.create;
public class RunnableTest implements Runnable {
@Override
public void run() {
System.out.println("线程运行");
}
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest();
Thread thread = new Thread(runnableTest);
thread.start();
}
}

2.3 Thread和Runnable的区别

Thread才是Java里对线程的唯一抽象,Runnable只是对任务(业务逻辑)的抽象,Thread可以接受任意一个Runnable的实例并执行。

2.3.1 注意事项

有些面试官会说实现线程的方式有三种 Thread、Runnable 以及Callable,但是按照java源码中Thread类中的注释说的实现类的防止只有两种,我们可以看下啊Thread类的源码

3. 线程的终止方式

下面我们看下线程的终止方式有哪些

3.1 线程自然终止

要么是run执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。

3.2 stop终止

暂停、恢复和停止操作对应在线程Thread的API就是suspend()resume()stop(),但是这些API是过期的,也就是不建议使用的 。

3.2.1 为什么不建议使用

不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。

​ 同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下,正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。

3.3 中断终止

推荐使用中断的方式来终止线程

​ 安全的中止则是其他线程通过调用某个线程A的interrupt()方法对其进行中断操作,,中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求,因为java里的线程是协作式的,不是抢占式的,线程通过检查自身的中断标志位是否被置为true来进行响应。

​ 线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false。

​ 如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait等),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false。

3.3.1 代码案例

 
package chapter01.stop;
/**
* 使用Runable的中断
*/
public class ThreadInterrupted implements Runnable {
private int i = 0;
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
i++;
System.out.println("线程正在运行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i > 10) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().isInterrupted());
}
}
public static void main(String[] args) {
new Thread(new ThreadInterrupted()).start();
}
}

3.3.2 注意事项

不建议自定义一个取消标志位来中止线程的运行

​ 因为run方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,状态位如果跨线程改变状态必须使用volatile来保证可见性。

  1. 一般的阻塞方法,如sleep等本身就支持中断的检查。
  2. 检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。

注意:处于死锁状态的线程无法被中断

3.4 状态位终止

状态位就是用一个变量来标识线程的运行状态,如果需要停止了就就改变状态位的状态,但是状态位一定要使用 volatile 关键字,否在可能造成多线程状态下的不可见

3.4.1 代码案例

3.4.1.1 使用volatile

使用volatile可以正常中断线程

 
package chapter01.stop;
public class ThreadFlag implements Runnable {
protected long i = 0;
private volatile boolean flag = false;
@Override
public void run() {
while (!flag) {
i++;
if (i > 100000) {
flag = true;
}
System.out.println(i);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadFlag threadFlag = new ThreadFlag();
new Thread(threadFlag).start();
}
}

3.4.1.2 不使用volatile

不使用volatile,会导致线程不能正常中断

 
package chapter01.stop;
public class ThreadInvisible implements Runnable {
protected long i = 0;
/**
* 不加volatile 会造成多线程的变量不可见,判断不会停止
*/
public boolean flag = false;
@Override
public void run() {
while (!flag) {
i++;
}
}
public static void main(String[] args) throws InterruptedException {
ThreadInvisible threadFlag = new ThreadInvisible();
new Thread(threadFlag).start();
Thread.sleep(1000);
threadFlag.flag = true;
}
}

3. run和start的区别

Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()其实只是new出一个Thread的实例,还没有操作系统中真正的线程挂起钩来。只有执行了start()方法后,才实现了真正意义上的启动线程。

​ start()方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()方法,start()方法不能重复调用,如果重复调用会抛出异常。

​ 而run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,也可以被单独调用。

4. 其他的线程相关方法

下面我们看下线程的其他方法有哪些

4.1 sleep方法

使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁,也不释放占用的资源

​ 也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据,注意该方法要捕捉异常。

​ 例如有两个线程同时执行(没有synchronized)一个线程优先级为MAX_PRIORITY,另一个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完毕后,低优先级的线程才能够执行;但是高优先级的线程sleep(500)后,低优先级就有机会执行了。

​ 总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。

4.1.1 代码案例

 
package chapter01.method;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadSleep {
public static void main(String[] args) {
sleep1();
sleep2();
}
public static void sleep1() {
Thread thread = new Thread(() -> {
System.out.println("xxxxxxxxxxxxxxxxxx");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
//thread.setPriority(100);
thread.start();
}
public static void sleep2() {
Thread thread = new Thread(() -> {
System.out.println("xxxxxxxxxxxxxxxxxx");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
//thread.set
thread.start();
}
}

4.2 join方法

把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行 。

​ 比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B

​ 注意: t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程

4.2.1 代码案例

 
package chapter01.method;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadJoin {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
for(int i=0;i<10;i++) {
System.out.println("111111111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
Thread thread2 = new Thread(()->{
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<10;i++){
System.out.println("2222222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
thread1.start();
thread2.setDaemon(true);
thread2.start();
}
}

4.3 yield方法

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。

​ 因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

​ yield()是将线程从运行状态变更为就绪状态,不会变为等待/睡眠/阻塞状态,注意:yeid方法是不释放资源的

4.3.1 代码案例

 
package chapter01.method;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadYield {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
// Thread.yield();
System.out.println("1111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
Thread thread2 = new Thread(()->{
System.out.println("22222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
});
thread1.start();
thread2.start();
}
}

5 线程的优先级

在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10

​ 在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。

​ 设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占,在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定

5.1 代码案例

 
package chapter01.priority;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
/**
* 执行的时候优先级越高 越容易执行到该线程
*/
public class ThreadPriority {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (true) {
System.out.println("1111111111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
Thread thread2 = new Thread(() -> {
while (true) {
System.out.println("22222222222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
thread1.setPriority(1);
thread2.setPriority(7);
thread1.start();
thread2.start();
}
}

6. 守护线程

Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。

​ 这意味着,当一个Java虚拟机中不存在Daemon线程的时候,Java虚拟机将会退出,可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程,我们一般用不上,比如垃圾回收线程就是Daemon线程

​ Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块并不一定会执行,在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑,也可以理解为等程序的所有的用户线程结束后,守护线程也将结束。

​ 注意:守护线程必须在start之前设置,否则会报错

6.1 代码案例

 
package chapter01.daemon;
import util.ThreadUtils;
import java.util.concurrent.TimeUnit;
public class ThreadDaemon {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (true) {
System.out.println("1111111111111");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("22222222222");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});
/* Thread thread3 = new Thread(() -> {
while (true) {
System.out.println("3333333333333");
ThreadUtils.sleep(1, TimeUnit.SECONDS);
}
});*/
thread1.setDaemon(true);
thread1.start();
thread2.start();
//thread3.start();
}
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式 -- 建造者模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…

WPF教程(四)--Dispatcher

一、Dispatcher介绍 微软在WPF引入了Dispatcher&#xff0c;那么这个Dispatcher的主要作用是什么呢&#xff1f; 不管是WinForm应用程序还是WPF应用程序&#xff0c;实际上都是一个进程&#xff0c;一个进程可以包含多个线程&#xff0c;其中有一个是主线程&#xff0c;其余的是…

Embodied AI 具身智能

大模型的时代的到来&#xff0c;现在的大模型的参数几乎是几何级数的上升。 谷歌和柏林工业大学的团队重磅推出了史上最大视觉语言模型——PaLM-E&#xff1a;An Embodied Multimodal Language Model。通才大模型PaLM-E&#xff0c;什么是通才模型&#xff0c;就是多任务统一学…

NeRF必读:NeuS--三维重建的milestone以及脑补的作者脑回路

前言 NeRF从2020年发展至今&#xff0c;仅仅三年时间&#xff0c;而Follow的工作已呈井喷之势&#xff0c;相信在不久的将来&#xff0c;NeRF会一举重塑三维重建这个业界&#xff0c;甚至重建我们的四维世界&#xff08;开头先吹一波&#xff09;。NeRF的发展时间虽短&#xf…

C++入门(前篇)

&#x1f525;&#x1f525;本章重内容 C入门 1.命名空间2. C输入&输出3. 缺省参数 简单的说一下C语言的出现是为了弥补C语言的不足 由于要补充的东西太多&#xff0c;所以就在C语言的基础上又出了C 所以我认为学习C的前提是你得懂C语言 C中可以使用C的大部分语法 可以这样…

Autowired注解与Resource注解的区别

两者的用法 其实这两个注解的作用都一样,都是在做bean的注入,在使用过程中,两个注解有时候可以替换使用 两者的共同点 Resource注解和Autowired注解都可以用作bean的注入.在接口只有一个实现类的时候,两个注解可以互相替换,效果相同. 两者的不同点 Resource注解是Java自身的…

技术分享 | OceanBase 手滑误删了数据文件怎么办

作者&#xff1a;张乾 外星人2号&#xff0c;现兼任六位喵星人的资深铲屎官。 本文来源&#xff1a;原创投稿 *爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注明来源。 手滑误删了数据文件&#xff0c;并且没有可替换的节点时&…

代码随想录第19天 | 235. 二叉搜索树的最近公共祖先 701.二叉搜索树中的插入操作 450.删除二叉搜索树中的节点

235. 二叉搜索树的最近公共祖先 var lowestCommonAncestor function(root, p, q) {// 使用递归的方法// 1. 使用给定的递归函数lowestCommonAncestor// 2. 确定递归终止条件if(root null) {return root;}if(root.val > p.val && root.val > q.val) {// 向左子…

set、map容器

一、set 1. set基本概念 简介: 所有元素都会在插入时自动被排序 本质: set/multiset属于关联式容器&#xff0c;底层结构是用二叉树实现。 set和multiset区别: set不允许容器中有重复的元素 multiset允许容器中有重复的元素 2. set构造和赋值 构造: set<T> st; …

安装WMware16、centos7记录

将一台闲置电脑安装虚拟机&#xff0c;计划给个8G内存&#xff0c;80G硬盘&#xff0c;打算安装WMware16&#xff0c;对系统要求是win10 一、将win7升级到win10 1.1、正版的win10安装U盘制作教程 https://www.bilibili.com/video/BV1AW411G7Lq/?vd_sourcecaf04463d06774efd…

Okio 网络提速

文章目录网络数据处理流程Page Cache传统 I/O 拷贝的性能问题零拷贝技术DMA 技术零拷贝技术分类mmapsendfilespliceDirect I/O零拷贝技术性能分析小结OkioOkio 的使用Okio 网络提速的原理Okio 总结总结网络数据处理流程 在讲 Okio 之前&#xff0c;为了能更好的了解 Okio 的优…

如何制定项目里程碑 它的作用体现在哪

制定项目里程碑是项目管理中的一个重要步骤&#xff0c;它可以帮助团队确立项目目标、分解任务、评估风险、规划资源和监控进度。在这篇文章中&#xff0c;我们将介绍如何制定项目里程碑和它的作用。 如何制定项目里程碑 制定项目里程碑需要遵循以下步骤&#xff1a; 1、确定…

《程序员面试金典(第6版)》面试题 10.01. 合并排序的数组

题目描述 给定两个排序后的数组 A 和 B&#xff0c;其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法&#xff0c;将 B 合并入 A 并排序。 初始化 A 和 B 的元素数量分别为 m 和 n。 示例: 输入: A [1,2,3,0,0,0], m 3 B [2,5,6], n 3 输出: [1,2,2,3,5,6] 说明: …

简单六步,帮助HR高效管理零工

AIHR发布的《2023人力资源趋势》中提到&#xff0c;过去HR往往只关注全职员工&#xff0c;忽略了其他劳动力生态系统成员&#xff0c;比如零工、外包员工和临时工等&#xff0c;而如今这些劳动力生态系统的成员在公司的服务交付中发挥着越来越重要的作用。△ 传统劳动力生态系统…

Java入坑之集合、流与序列化

一、集合 1.1集合定义 集合概念&#xff1a; 保存和盛装数据的容器&#xff0c;将许多元素组合成一个单一单元的容器对象。集合&#xff0c;可用于存储/检索/操作/传输/聚合数据集合框架&#xff1a; 表示和操作集合的体系&#xff0c;包括接口、实现类&#xff0c;集合框架的…

【Nav2】Ubuntu18+ROS2 Eloquent跑通Navigation2仿真示例

【背景】 打算通过Navigation2来了解ROS2的核心两大件——LifeCircle和BehaviorTree&#xff0c;结果根据官网的教程一顿鼓捣&#xff0c;这个Turtlbot3的仿真就是跑不起来&#xff0c;这怎么能忍&#xff1f;虽然在Ubuntu20上使用Foxy版本可以非常容易就跑通demo&#xff0c;…

3年外包终上岸,我只能说:但凡有点机会,千万别去外包...

我大学学的是计算机专业&#xff0c;毕业的时候&#xff0c;对于找工作比较迷茫&#xff0c;也不知道当时怎么想的&#xff0c;一头就扎进了一家外包公司的软件测试岗&#xff0c;一干就是3年。现在终于跳槽到了互联网公司了&#xff0c;我想说的是&#xff0c;但凡有点机会&am…

behaviac —— Win10下Vs2017编译“腾讯行为树“源码

简介 - 腾讯行为树 behaviac是游戏AI的开发框架组件,也是游戏原型的快速设计工具。支持全平台,适用于客户端和服务器,助力游戏快速迭代开发 。编辑器可以运行在PC上,操作方便直观可靠,支持实时和离线调试;编辑器可以导出xml,bson等多种格式,更可以导出C++、C#源码,提供…

免费1年服务器,部署个ChatGPT专属网页版

作者&#xff1a;小傅哥 博客&#xff1a;https://bugstack.cn 沉淀、分享、成长&#xff0c;让自己和他人都能有所收获&#xff01;&#x1f604; 白皮袄个免费1年服务器&#xff0c;部署个ChatGPT专属网页版&#xff01; api.openai.com port 443: Connection timed out 你是…

Spring Security --- 基于内存模型创建用户角色

授权实现方式 基于内存模型实现授权基于默认数据库模型实现授权基于自定义数据库模型实现授权 基于内存模型创建用户角色 在Spring Security4.x版本中&#xff0c;登陆的用户有一个默认的ROLE_USER角色但是在Spring Security5.x版本中&#xff0c;把这个默认的角色给去掉了需要…