【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的常见属性

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

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

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

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

java 复制代码
Thread t = new Thread(() ->{

});

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


java 复制代码
t.start();

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


java 复制代码
Thread t = new Thread(() ->{

});
t.start();
Thread.sleep(1000);

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


java 复制代码
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)自己实现控制线程结束的代码->设置循环条件

java 复制代码
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();
java 复制代码
while (!Thread.currentThread().isInterrupted()) {
    //如果线程要继续执行,则循环会继续下去
    //如果线程中断,则循环终止
}

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

java 复制代码
    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)这样的设定实际上还是为了将是否要中断交给线程自己

java 复制代码
    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()之后的逻辑,代码如下:

java 复制代码
    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会直接返回

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

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

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

三、线程休眠sleep()

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

java 复制代码
    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条支线

相关推荐
数据小爬虫@6 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
ZJ_.8 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
Narutolxy13 分钟前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader21 分钟前
全面解析 Golang Gin 框架
开发语言·golang·gin
禁默31 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood38 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑41 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb421528743 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶44 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
qq_433618441 小时前
shell 编程(二)
开发语言·bash·shell