Java - 线程的属性和方法(sleep()、interrupt()中断线程、 join()控制线程顺序)

Thread类是JVM用来管理线程的一个类,每个线程都有唯一的一个Thread对象与之关联。JVM使用一些属性描述线程对象,用一些类方法管理对象。

目录

一、线程的属性

二、 线程的方法

1.  线程的构造方法

2. 获取线程对象

3. sleep方法

4. 中断线程

5. join控制多线程顺序

🔊 join()总结   


一、线程的属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否为后台线程isAlive()

是否存活

isDaemon()
是否中断isInterrupted()
  • 线程的ID

获取到的是线程在JVM中的身份标识,是线程的唯一标识,不同线程不会重复

注意理解“唯一”的含义。

内核的PCB、用户态的线程库、JVM中,只要用到线程且需要对其进行管理的地方,都会对线程进行身份标识。三个标识各不相同,但目的一样。每个地方的身份标识都是唯一的。

  • 线程的名称

在Thread构造方法里取的名字

  • 线程的状态

获取线程当前处于就绪还是运行等其他状态,是JVM当中设立的状态

(JVM中的线程有6种,具体类别以及状态转换会在另一篇博客中总结~ )

  • 线程优先级

获取到线程调度的优先级 

 获取 ID、名称、状态、优先级代码示例: 

public class demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            while(true){
                for(int i=0;i<10;i++){
                    System.out.println("新线程"+i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        System.out.println("线程创建前状态: "+thread.getState());
        thread.start();
        //Thread.sleep(500);
        System.out.println("线程创建后状态: "+thread.getState());
        System.out.println(thread.getPriority());
        System.out.println( thread.getName());
        System.out.println(thread.getId());
    }
}
  • 线程是否为后台线程

线程分为前台线程和后台线程,后台线程又称守护线程。我们常说的主线程就是前台线程。

前台线程的结束决定进程是否结束。不管后台线程如何,前台线程全部结束,进程就结束。

创建出来的线程默认也是前台线程,不过通过设置 thread.setDaemon(true) 可以将前台线程转变为后台线程。

代码示例: 

public class demo1 {
    public static void main(String[] args) {
        Thread thread=new Thread(()->{
            while(true){
                for(int i=0;i<10;i++){
                    System.out.println("新线程"+i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.setDaemon(true);
        //不设置thread.setDaemon(true),主线程运行结束时,新线程thread一直在运行
        //设置thread.setDaemon(true)后,主线程运行结束,进程直接结束
        //注意:必须 要在创建线程之前 就将thread设置为后台线程
        thread.start();
        System.out.println(thread.isDaemon());
    }
}

运行截图:

  • 线程是否存活
  • 线程是否被中断

获取到线程当前是否被中断

二、 线程的方法

1.  线程的构造方法

方法1&2就是我们创建线程时所用的两种构造方法,方法3&4创建线程的原理与1&2一样.

方法3使用示例:

class MyThread extends Thread {
    String s;
    public MyThread(String str) {
        this.s=str;
    }
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println(s + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class demo1 {
    public static void main(String[] args) {
        MyThread thread=new MyThread("a线程");
        thread.start();
    }
}

 方法4使用示例:

class MyRunnable implements Runnable {
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println("新线程" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class demo1 {
    public static void main(String[] args) {
        MyRunnable runnable=new MyRunnable();
        Thread thread=new Thread(runnable,"a线程");
        thread.start();
    }
}

区别:方法1、2创建的线程用的是默认名字;使用方法3、4,可以自己给线程取个名字

再次使用 jconsole 工具

2. 获取线程对象

使用 Thread.currentThread() 方法,返回当前线程对象

3. sleep方法

线程运行的速度是非常快的,我们有时候观察线程打印就需要借助sleep让线程的打印慢下来。

Thread.sleep()是Thread类的一个静态方法,使当前线程休眠,进入阻塞状态(暂停执行),如果线程在睡眠状态被中断,将会抛出IterruptedException中断异常。

🔊 sleep是怎么起作用的呢?

每个线程有自己的PCB,操作系统将多个PCB组合成双向链表进行管理。另外,操作系统中又多个链表,不同状态的PCB在不同的链表上。比如多个处于就绪状态的线程集中在一个链表上,我们把这些线程的组合叫做就绪队列。

start后,没有sleep前,线程是在就绪队列上的。

使用sleep,线程从原来的就绪队列调度回阻塞队列

4. 中断线程

🙋🏻‍♂️为什么需要中断线程?

线程一旦进到工作状态,就会按照任务步骤进行工作,不完成是不会结束的。但有时系统出现一些问题,需要停止线程工作,就需要中断线程。

🙋🏻‍♀️ 怎么中断线程?

中断线程的方式有两种,下面我们分别来看这两种方法的具体操作...

① 自己设置标志位控制线程中断

public class demo1 {
    public static boolean isQuit=false;
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
           while(!isQuit){
               System.out.println("新线程运行中...");
               try {
                   Thread.sleep(1000); //使用sleep,让线程执行任务的速度慢下来便于观察
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
            System.out.println("线程结束");
        });
        thread.start();
        //Thread.sleep(1000);//使用sleep,可以阻塞主线程。间接影响到新线程任务执行的次数
        isQuit = true;
    }
}

  运行结果:

分析运行结果:

由于主线程main与新建线程thread并发运行,当主线程运行到 isQuit = true 位置(该代码作用是改变新线程的while循环条件)时,新线程运行到哪里了不确定。

此时新线程运行的位置,简单来说有两种情况:

① 新线程运行到while循环条件前,执行到while循环条件代码处退出

  • 一次任务都没执行
  • 已经执行过几次任务

② 新线程运行到while循环条件后,下一次执行到while循环条件处退出

  • 至少会执行一次任务

一句话总结:不论新线程执行在while循环前还是while循环后,都会影响到while循环条件从而影响代码执行。在这几种情况下,随着新线程执行任务次数的不同,运行结果不同。

② 利用系统自带的标志位控制线程中断

public class demo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("新线程运行中...");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   //===== 默认异常处理 ======
                   //打印异常 
                   e.printStackTrace();
                   
                   //===== 自己写异常处理 ======
                   //[1] 直接退出
//                   break;
                   
                   //[2] 稍后退出, 此处的 sleep 可以换成任意的用来收尾工作的代码
//                    try {
//                        Thread.sleep(3000);
//                    } catch (InterruptedException ex) {
//                        ex.printStackTrace();
//                    }
//                    break;
                   
                   //[3] 不退出,什么都不干就行
                   
               }
            }
            System.out.println("新线程结束");
        });
        thread.start();
        Thread.sleep(1000);
        System.out.println("控制新线程结束");
        thread.interrupt();
    }
}

运行结果:

注意:

自己设置的标志位,改变变量isQuit的值后,不论新线程当前执行位置在哪里,最终都会表现为“循环条件改变而导致程序退出”。但是程序自带的标志位则不然...

分析运行结果:

调用程序自带的标志位,新线程执行到while前和while后,会有两种表现方式:

( 在这里将while前的线程的状态称为非阻塞状态,while后线程的状态称为阻塞状态 )

① 通过改变标志位处理中断

     当新线程处于非阻塞状态(未运行到Thread.sleep),主线程与此同时运行的interrupt()         会导致循环条件改变而直接退出循环;

② 通过Thread.sleep的异常处理机制处理中断

     当线程处于阻塞状态(运行到Thread.sleep),说明已经执行完了while循环条件那一段         代码,那么主线程与此同时运行的interrupt()不会影响到while循环条件。但是程序可以通       过对Thread.sleep()捕捉到的 interrupt 中断异常进行对应的处理,实现程序的中断。

一句话总结:线程处于非阻塞状态,影响循环条件;线程处于非阻塞状态,Thread.sleep会处理中断异常,通过自定义异常处理,实现不同的中断线程方式。

5. join控制多线程顺序

🙋🏻‍♂️ 为什么使用join ?

由于线程调度的随机性,多线程的执行顺序十分随机。有些情况,我们需要线程按照我们想要的执行顺序去执行。就需要用到join方法

🙋🏻‍♀️ 怎么使用join ?

使用join的格式:线程对象.join

join的作用: 阻塞 调用了"线程对象.join" 的线程

代码示例1:谁来调用join?谁调用join有效果?

public class demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1=new Thread(()->{
            for(int i=0;i<10;i++){
                System.out.println("a线程"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2=new Thread(()->{
            for(int i=0;i<10;i++){
                System.out.println("b线程"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });


        //测试:  ??? 谁来调用join有效果
//        //第一组
//        thread1.start();
//        thread2.join();
//        thread2.start();
//        //第二组
//        thread1.start();
//        thread1.join();
//        thread2.start();
//        //第三组
//        thread1.start();
//        thread2.start();
        //测试发现: 第一组第三组结果相同,与第二组不同
        //结论:只有使用已创建的线程对象去调用join,才可以起作用
        //分析运行结果:在第二组中,thread1.join是怎么起作用的呢?
        //   主线程调用thread1.join,阻塞主线程。当线程1运行结束,阻塞接触。主线程继续运行调用thread2.start
        //   线程2和主线程开始交替运行


        while(true){
            for(int i=0;i<10;i++){
                System.out.println("主线程"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

线程.join,这个线程需要已经创建,否则没有意义。

我们后续提到的线程.join,都是已经创建出来的线程

代码示例2:正确调用join后的运行结果分析

public class demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1=new Thread(()->{
            for(int i=0;i<3;i++){
                System.out.println("a线程"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread1.join();//在主线程中调用join,阻塞主线程

        while(true){
            for(int i=0;i<3;i++){
                System.out.println("主线程"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

输出结果: 

现有线程thread1、主线程两个线程,运行过程:

主线程与thread1线程被随机调度,开始并发运行

->

主线程中调用thread1.join,主线程阻塞;此时thread1不确定运行是否结束,有两种情况:

① thread1没有运行完,join方法等待thread1结束后接触阻塞

②thread1已经运行结束,join方法不再等待直接返回

->

主线程阻塞解除后,继续运行

-> 

进程结束

关键看被等待的线程thread1运行是否结束

public class demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1=new Thread(()->{
            for(int i=0;i<10;i++){
                System.out.println("a线程"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2=new Thread(()->{
            for(int i=0;i<10;i++){
                System.out.println("b线程"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        //测试二: ??? 调用顺序是否会影响阻塞状态
        //第一组
//        thread1.start();
//        thread2.start();
//        thread1.join();
//        thread2.join();
        //第二组
//        thread1.start();
//        thread2.start();
//        thread2.join();
//        thread1.join();
//        //第三组
//        thread1.start();
//        thread1.join();
//        thread2.start();
//        thread2.join();
        //测试发现: 第一组第二组结果相同,与第三组结果不同
        //结论:当两个join在一起被调用时,此时join的顺序不会影响阻塞最终结果
        
        while(true){
            for(int i=0;i<10;i++){
                System.out.println("主线程"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

第三组测试前面两个线程的运行分析是一样的,现在来分析一下第二组和第三组。

现有线程thread1、thread2、主线程三个线程,运行过程:

thread1(t1)、thread2(t2)、主线程被随机调度,开始并发运行

-> 

t1.join - t2.join 还是 t2.join - t1.join 关键看哪个线程先结束

① 如果t1先结束t2后结束

     当t1结束的时候,主线程中的t1.join已经解除阻塞,继续执行t2.join又被阻塞。然后t2结         束,主线程的t2.join也结束阻塞。主线程继续执行

② 如果t2先结束t1后结束

当t2结束的时候,由于t1没结束,主线程执行到t1.join被阻塞。当t1结束之后,t1.join解除阻塞,主线程继续执行到t2.join。由于t2已经结束了,t2.join不用再等待t2于是也就不阻塞主线程直接返回。主线程继续执行。

->

进程结束

这里的关键在于被等待的线程t1、t2谁先结束。

另外,其实不管谁先结束,打印出来的运行结果都差不多,都是t1、t2两个线程交替打印。而不是某一个线程运行结束,其他线程才开始打印。

🔊 join()总结   

【 join 的行为】

阻塞调用 '线程.join' 的线程

1. 如果被等待的线程还没执行完,就阻塞等待

2. 如果被等待的线程已经执行完了,直接就返回

拓展:阅读资料时,看到过一句话:“join是等待当前的线程执行结束”。

我个人认为这句话不是在所有场合都适用。比如当线程1内部调用线程1.join,会导致线程1自己阻塞自己,和这句话放在一起有点不好理解。所以私以为 join 的作用说成是“阻塞调用它的线程”更为合适...

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值