在上篇文章主要引入了线程的概念,并且介绍了创建线程的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条支线

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部