欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 明星 > 【JavaEE】多线程(1)

【JavaEE】多线程(1)

2025/5/9 15:30:45 来源:https://blog.csdn.net/m0_74270127/article/details/143610286  浏览:    关键词:【JavaEE】多线程(1)


在上篇文章主要引入了线程的概念,并且介绍了创建线程的5种方式,这篇文章将继续对线程的知识进行讲解

一、Thread类及其常见方法

通过上篇文章可以知道:每个线程都对应一个唯一的Thread对象,那么Thread类中有哪些常见的方法

1.1 Thread的构造方法 

方法

说明

Thread()

创建线程对象

Thread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用Runnable对象创建线程对象,并命名

给线程起名字只是方便调试的时候知道哪个线程可能出问题了,对线程的运行效果没有影响

1.2 Thread的常见属性

属性获取方法
IDgetId()
名称

getName()

状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()
  • ID: 线程的唯一标识,不同的线程不会重复,注意这里的id和系统中pcb上的id是不同的,是jvm自己搞定一套id体系
  • 名称:就是创建线程时给其起的名字,默认为Thread-0,Thread-1......
  • 状态:后续讲解,有就绪状态,阻塞状态等
  • 优先级:优先级⾼的线程理论上来说更容易被调度到
  • 是否后台线程:线程分为前台线程后台线程
  1. 前台线程:前台线程运行没有结束的话,那么其所在的整个进程也一定不会结束
  2. 后台线程:后台线程运行不管有没有结束,都对进程的结束与否没有影响

前台线程可有多个,多个前台线程必须最后一个天台线程结束,整个进程才会结束,卖弄线程就属于前台线程,另外我们自己通过那5种方式创建出来的线程默认为前台线程,可以通过setDaemon方法将其设置为后台线程(一般不期望这个线程影响进程结束的就会将其设为后台线程)

  • 是否存活:指的是系统中的线程(PCB)是否还存在

注意一个Thread对象对应唯一的一个线程 但是他们两个生命周期并不完全相同

Thread t = new Thread(() ->{});

上述代码是创建了Thread对象,此时内核中的PCB还没有创建


t.start();

执行完start方法才是真正创建线程,此时PCB才被加入到链表中


Thread t = new Thread(() ->{});
t.start();
Thread.sleep(1000);

上述代码中,由于线程没有任务执行,所以start方法执行完后线程很快就结束了(内核中的PCB被销毁了)但是由于sleep()方法使主线程睡眠了1秒钟,所以t指向的Thread对象还存在


Thread t = new Thread(() ->{try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}
});
t.start();
t = null;

上述代码中,上述代码中线程还没有结束,t指向的Thread对象就被回收了

总结:Thread对象和PCB的生命周期会出现不同,所以要通过isAlive()判断线程是否存活

  • 是否被中断

中断线程只是在提醒线程要终止了,但是实际上要不要终止还要看线程自己决定,详细介绍如下

这里讲解2种中断线程的方法

1)自己实现控制线程结束的代码->设置循环条件

public class Demo {public static boolean isRunning = true; //循环条件public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() ->{while(isRunning) {System.out.println(isRunning);try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();Thread.sleep(3000);isRunning = false; }
}

上述代码通过设置isRunning来结束t线程,正如刚开始说的,终止线程只是一个提醒,比如上述代码如果压根就没有拿isRunning来做循环条件,那不管外界怎么干涉线程都不会终止

除此之外还存在一个问题:t线程中如果沉睡10秒甚至更长,此时main线程是无法及时的把t线程终止掉,所以接下来介绍第二种也是比较推荐的方法

2)使用Thread提供的interrupt方法和isInterrupted方法来实现上述效果

刚刚我们自己定义了一个标志位,其实Thread里面内置了一个标志位:isInterruptted方法

/*** 线程内置的标志位* @return true 表示线程要终止了 flase 表示线程要继续执行*/
Thread.isInterrupted();
while (!Thread.currentThread().isInterrupted()) {//如果线程要继续执行,则循环会继续下去//如果线程中断,则循环终止
}

t.interrupt();通过这个方法就可以设置Thread.isInterrupted()的值为true,其默认为false,除了能设置标志位的值,还可以唤醒sleep方法,下面举一个例子:

    public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();Thread.sleep(3000);t.interrupt();}

3s过后,执行结果如下:

这里3s过后抛出了catch中的RuntimeException异常,如果将catch中的语句修改为e.printStackTrace();

3s过后,执行结果如下:

可以看到t线程仍然在执行,那么这里就有一个疑问:明明修改了标志位使得循环条件为false,为什么线程仍然在执行?

其实是因为sleep,当线程正在sleep,sleep被唤醒的同时,就会清除刚才的标志位(改回false)这样的设定实际上还是为了将是否要中断交给线程自己

    public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {break;}}});t.start();Thread.sleep(3000); //3s过后t线程会进入catch 报一个RuntimeException异常t.interrupt();}

如上述代码,将catch中的语句改为break;此时当sleep被唤醒时,就会直接结束

二、线程等待: join()

之前提到过,线程的执行是无序的,那么程序执行的结果就是随机的,但是我们希望可以控制线程的执行顺序从而得到稳定的执行结果,因此引入线程等待来确定线程结束的先后顺序

通过join()方法来实现线程等待

用法:假如在main线程里使用t.join(); 其效果就是main线程等待t线程结束后才会继续执行t.join()之后的逻辑,代码如下:

    public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("hello thread");});t.start();t.join();System.out.println("hello main");}

上述代码中main线程会等待t线程执行结束后再执行t.join();之后的逻辑,执行结果为:

main线程调用join()有两种可能:

1.main线程调用t.join()后,如果t线程已经结束了,那么join会直接返回

2. 如果线程没有结束,就会使main线程处于阻塞状态(阻塞:线程暂时不参与CPU调度)

接下来再介绍两种带参的join()

void join()

等待线程结束再继续执行(死等)
void join(long milis)等待时间到了,会继续执行
void join(long milis, int nanos)等待时间到了,会继续执行,但时间会精确到纳秒

三、线程休眠sleep()

sleep()控制的使线程休眠的时间而不是sleep()前后两个代码的间隔时间,也可以理解为线程实际休眠时间往往大于sleep()中的参数设定的时间,举一个例子:

    public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(1000);System.out.println(System.currentTimeMillis());}

执行结果如下:

时间间隔大于1000ms

四、线程的状态

4.1 介绍状态

这里介绍6种线程状态

  • NEW:安排了工作但还未开始执行(还没有start)
  • RUNNABLE:就绪状态,该状态分为线程正在CPU上执行线程可以随时调度到CPU上执行
  • BLOCKED:进行锁竞争的时候产生的阻塞状态,后面再说
  • WAITING:死等进入的阻塞状态
  • TIMED_WAITING:带有超时时间的等进入的阻塞状态
  • TERMINATED:线程终止,内核中的线程已经销毁了

4.2 状态转移

 1条主线,3条支线

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词