Thread类及常见方法

简介: Thread类及常见方法

基本概念

Thread类是JVM用来管理线程的一个类,换句话说,每个线程都有一个唯一的Thread对象与之关联.

用上一篇中的例子来看,每个执行流,也需要有一个对象来描述,而Thread类的对象就是用来描述一个线程的执行流的,JVM也会将这些Thread对象组织起来,用于线程调度,线程管理.

Thread的常见构造方法

方法 说明
Thread() 创建线程对象
Thread(Runnable target) 使用Runnable对象创建线程对象
Thread(String name) 创建线程对象,并命名
Thread(Runnable target, String name) 使用Runnable对象创建线程对象,并命名
[了解]Thread(ThreadGroup group, Runnable target)

线程可以用来分组管理(了解即可)

注:一般自己创建的线程(没有命名),默认为Thread-0,1,2,3...

这个是不太好区分的,推荐利用上面命名的构造方法

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

Thread的几个常见属性

属性 获取方法
ID getId()
名称 getName()
状态

getState()

优先级 getPriority()
是否是后台线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()

(1)ID是线程的唯一标识,不同线程是不会重复的(因为ID是jvm自动分配的身份标识,会保证身份的唯一性).

(2)名称是各种调试工具都要用到的重要属性

(3)状态表示线程当前所处的一个情况,之后我们将详细说明

(4)优先级高的线程理论上更容易被调度到(这个属性用的很少)

(5)isDaemon设置为true就是后台, daemon意为守护,可叫做守护线程(非常抽象),但更广泛的被叫为后台线程,与后台线程相对,还有前台线程.后台线程其运行不会阻止进程结束,前台线程其运行会阻止进程结束(jvm中内置的线程都是后台线程,不会阻止进程结束)关于后台线程需要记住一点:JVM会在一个进程的所有非后台线程结束以后,才会结束运行.     而我们代码创建的线程默认是前台线程,会阻止进程结束,只要前台线程没有执行完,进程就不会结束.即使main已经执行完毕.

(6)是否存活,最简单的理解,就是run方法是否运行结束了,高级的理解:就是表示内核中的线程(PCB)是否还存在

代码示例:

public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
           for(int i = 0; i < 10; i++) {
               try {
                   Thread.sleep(1000);
                   System.out.println(Thread.currentThread().getName() + ": 我还活着");
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
            System.out.println(Thread.currentThread().getName() + ": 我即将死去");
        }, "一个不愿透漏姓名的神秘线程");
        System.out.println(Thread.currentThread().getName() + ": ID: " + thread.getId());
        System.out.println(Thread.currentThread().getName() + ": 名称: " + thread.getName());
        System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());
        System.out.println(Thread.currentThread().getName() + ": 优先级: " + thread.getPriority());
        System.out.println(Thread.currentThread().getName() + ": 后台线程: " + thread.isDaemon());
        System.out.println(Thread.currentThread().getName() + ": 活着: " + thread.isAlive());
        System.out.println(Thread.currentThread().getName() + ": 被中断: " + thread.isInterrupted());
 
        thread.start();
        while(thread.isAlive()) {
            System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());
        }
    }
}

启动一个线程 - start()

之前我们已经看到了如何通过覆写run方法创建一个线程对象,但是线程对象被创建出来并不意味着线程就开始运行了.

start和run方法的区别.

1.覆写run方法是提供线程要做的事情的指令清单,调用run()实际上还是在main线程中,根本还是串行执行

2.使用start()start方法才是真正意义上在操作系统底层创建了一个新线程.

终止一个线程

认为是让线程run方法(入口方法)执行完毕

李四是X公司的一名员工,老板要求按步骤给某人转账.

按理来说他会正常按照行动指南上的步骤去进行工作,不完成是不会结束的.但有时我们需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如何通知李四停止呢?这就涉及到了我们停止线程的方式了.

目前常见的有以下两种方式:

1.通过共享的标记来进行沟通

2.调用interrupt()方法来通知

示例1:使用自定义的变量作为标志位.

public class ThreadDemo1 {
 
    public static boolean isQuit = false;
 
    public static void main(String[] args) {
 
        Thread t = new Thread(() -> {
            while(!isQuit) {
                System.out.println(Thread.currentThread().getName() + ": 别管我,我忙着转账呢!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": 啊!险些误了大事!");
        }, "李四");
 
        System.out.println(Thread.currentThread().getName() + ": 让李四开始转账.");
        t.start();
        try {
            Thread.sleep(10 * 1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
 
        System.out.println(Thread.currentThread().getName() + ": 老板来电话了,得赶紧通知李四对方是个骗子!");
        isQuit = true;
    }
}

这个写法,不够优雅,Thread提供了更优雅的方法供我们选择.

示例2:使用Thread.interrupted()或者Thread.currentThread().isInterrupted()代替自定义标志位 .

Thread内部包含了一个boolean类型的变量作为线程是否被中断的标记.

方法 说明
public void interrupt()

中断对象关联线程,如果线程正在阻塞,

则抛出InterruptedException异常并清除中断标志

public static boolean interrupted() 判断当前线程的中断标志位是否设置,并清除中断标志位
public boolean isInterrupted() 判断对象关联的线程的标志位是否设置,调用后不会清除中断标志

使用thread对象的interrupted()方法通知线程结束

public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
           while(!Thread.currentThread().isInterrupted()) {
               System.out.println(Thread.currentThread().getName() + ": 别管我,我忙着转账呢!");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
                   System.out.println(Thread.currentThread().getName() + ": 有内鬼,终止交易! ");
                   //注意此处的break,后面要讲
                   break;
               }
           }
            System.out.println(Thread.currentThread().getName() + ": 啊!险些误了大事");
        }, "李四");
 
        t1.start();
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
 
        System.out.println(Thread.currentThread().getName() + ": 老板来电话了,得赶紧通知李四对方是个骗子");
        t1.interrupt();
    }
}

Thread收到通知的方式有两种:

1. 如果线程因为调用wait/join/sleep等方法而阻塞挂起,则会以InterruptedException异常的形式通知,清除中断标志.

(在执行sleep的过程中,调用interrupt,大概率sleep休眠时间未到,被提前唤醒了.)

提前唤醒,会出现两件事:1.抛出InterruptedException异常;2.清除Thread对象的isInterrupted标志位(通过interrupt已经设为true了,但是sleep提前唤醒操作,又会将标志位设为false)

sleep清除标志位,是为了给程序员更多"可操作空间"

当出现InterruptedException时,要不要结束线程取决于catch中代码的写法,可以选择忽略这个异常,也可以跳出循环结束的线程

(1)加上break:让线程立即结束

(2)不加break:让线程不结束,继续执行

(3)写一些其它的代码再加break :让线程执行一些逻辑后结束

在实际开发中catch中的代码

(1)尝试自动恢复,就尽量自动恢复,比如出现了网络通信相关的异常,就在catch中尝试重连

(2)记录日志(将异常信息记录到文件中):有些问题不严重(不需要立即解决),先记下来,等程序猿有空再解决

(3)发出报警:比较严重了,将以短信电话等形式通知程序员,表明需要立即解决

(4)少数业务正常逻辑需要依赖catch

2.否则,只是一个内部的一个中断标志被设置,thread可以通过

Thread.currentThread().isInterrupted判定指定线程的中断标志被设置,不清除中断标志.这种方式通知收到更及时,即使线程正在sleep也可以马上收到.

等待一个线程 - join()

有时,我们需要等待一个线程完成它的工作之后,才能进行自己的下一步工作.而不是通过强行终止这个线程的方法(实际上强行终止一个线程也不科学).例如,张三只有等李四转账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束.

举例:

public class TestDemo2 {
    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
           for(int i = 0; i < 5; i++) {
               try {
                   System.out.println(Thread.currentThread().getName() + ": 我还在工作! ");
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
            System.out.println(Thread.currentThread().getName() + ": 我结束了");
        };
 
        Thread t1 = new Thread(target, "李四");
        Thread t2 = new Thread(target, "王五");
        System.out.println("先让李四开始工作");
        t1.start();
        t1.join();
        System.out.println("李四工作结束了,王五开始工作");
        t2.start();
        t2.join();
        System.out.println("王五工作结束了");
    }
}
方法 说明
public void join() 等待线程结束(就是死等,等不到不走)
public void join(long millis) 等待线程结束(最多等millis毫秒)
public void join(long millis, int nanos) 同理,可设置精度

获取当前线程的引用

利用下面的方法可以获得当前线程的实例,哪个线程调用就是哪个线程的实例.相信你们已经非常熟悉了.

public static Thread currentThread();

休眠当前线程

也是我们比较熟悉的方法,要记得:因为线程调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的.

方法

说明

public static void sleep(long millis)throws InterruptedException 休眠当前线程millis毫秒
public static void sleep(long millis)throws InterruptedException 更高精度
相关文章
|
5月前
面试官:除了继承Thread类和实现Runnable接口,你知道使用Callable接口的方式来创建线程吗?
面试官:除了继承Thread类和实现Runnable接口,你知道使用Callable接口的方式来创建线程吗?
19 0
面试官:除了继承Thread类和实现Runnable接口,你知道使用Callable接口的方式来创建线程吗?
|
7月前
|
Java API Go
线程介绍,线程与进程区别,如何使用多线程,Thread类,Runnable接口,补充知识(方法重载,方法重写)
线程介绍,线程与进程区别,如何使用多线程,Thread类,Runnable接口,补充知识(方法重载,方法重写)
|
7月前
|
Java 程序员 调度
了解Thread类的其他一些方法及常见属性
了解Thread类的其他一些方法及常见属性
33 0
|
8月前
Thread 类的基本用法
比较推荐:使用 lambda 表达式创建线程的时候不用重写 run 方法。 不需要显式重写run方法的原因是因为线程的目标方法已经在Lambda表达式中定义了。Lambda表达式是一种用于创建匿名函数的语法糖,它可以将一个方法(或一段代码块)包装为一个函数对象。当您使用Lambda表达式创建线程时,Lambda表达式的内容会被视为线程执行的任务,这个任务会自动成为run方法的实现。
39 0
|
8月前
|
Java 调度
Thread类的方法
Thread类的方法
25 0
|
9月前
|
调度
Thread 类的基本方法
Thread 类的基本方法
50 0
|
9月前
|
Java
多线程的创建的方式一:继承于Thread类
多线程的创建的方式一:继承于Thread类
30 0
|
10月前
Thread类的基本用法
Thread类的基本用法