【javaEE】多线程(初阶)Part2

目录

  • 前言
  • 一、Thread类及主要方法
    • 1. Thread常见构造方法
    • 2. Thread的几个常见属性
    • 3. 线程启动
    • 4. 线程中断
    • 5. 线程阻塞
    • 6. 线程休眠
  • 二、线程状态
  • 三、相关代码(看!)
  • 【补充】Java中获取随机数
  • THINK


前言

未来美不美,取决于你现在拼不拼!

本文主要介绍:Thread类及主要方法、线程状态。


一、Thread类及主要方法

  1. 【复习】进程包含了线程,一个线程对应一个PCB,一个进程对应一组PCB(内存指针和文件描述符表都是相同的一份,但是状态、优先级、上下文、记账信息是每个线程独立的)
    (一个进程对应一组PCB:pid其实是不一样的,但是属性tgrp_id是相同的)

  2. 每个线程都有一个唯一的 Thread 对象与之关联。

1. Thread常见构造方法

  1. 可以在创建线程的时候给线程起名字(这个名字是允许重复的),目的就是为了方便程序员来调式。
    如果不手动起名字,默认JVM会按照thread-0、thread-1、…这样子起名字,但是可读性不好。

  2. Thread常见的构造方法
    【javaEE】多线程(初阶)Part2_第1张图片
    示例:

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

2. Thread的几个常见属性

  1. Thread的常见属性
    【javaEE】多线程(初阶)Part2_第2张图片

① ID 是线程的唯一标识,不同线程不会重复
② 名称是各种调试工具用到
③ 状态表示线程当前所处的一个情况
④ 优先级高的线程理论上来说更容易被调度到
⑤ 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行
⑥ 是否存活,即简单的理解,为 run 方法是否运行结束

  1. 常见属性相关介绍
    ① getId() 是java给Thread对象安排的身份标识,和操作系统内核中的PCB的pid以及和操作系统提供的线程API中的线程id都不是一回事儿。
    ② 身份标识可以有多个,在不同的环境下使用不同的标识。

一个线程:
①在JVM中有一个id
②在操作系统的线程API中有个id
③在内核PCB中还有个id

(但是这些id效果其实是一样的,只是在不同环境中使用)

③ 咱们默认创建的线程是“前台线程”,前台线程会阻止线程退出
如:如果main运行完了而前台线程还没运行结束,则进程不会退出。
如果是后台线程,后台线程是不阻止进程退出的。
如:如果main等其他的前台线程执行完了,此时即使后台线程没有执行完进程也会退出(主线程main也是前台线程
(前台线程:一般是重要的线程,也就是数据较为重要,一定要完成才能够结束的那种。如:转账)
④ 可以使用setDaemon(true)来设置为后台线程。—— 在start之前设置!! 一旦程序启动就没办法设置。
⑤ isAlive() 是判断线程是否存活 ,也就是判断内核中的线程是否还存在
Thread对象虽然和内核中的线程是一一对应的关系,但是生命周期并非完全相同。

  • Thread对象已经创建后内核里的线程还不一定有,调用start方法后内核线程才有;当内核线程执行完了(run运行完了),内核的线程就销毁了,但是Thread对象还在。

3. 线程启动

  1. 调用start才会真正创建线程,不调用start就没有创建线程(注意这里指的是在内核里创建PCB)
  2. 调用 start 方法, 才真的在操作系统的底层创建出一个线程。
  3. 注意理解:run和start的区别【经典面试题】
    答:① 直接调用run并没有创建线程,只是在原来的线程中运行的代码,只是相当于调用方法
    ② 调用start则是创建了线程,在新线程中执行代码(和原来的线程是并发执行的(并发+并行))

4. 线程中断

  1. 线程的中断
    run方法执行完则线程结束,那么有没有办法让线程提前结束呢?
    ——就是通过线程中断的方式来进行的(本质上仍然是让run方法尽快结束,而不是让run执行一半就强制结束)

  2. 方法:① 直接自己定义一个标志位,作为线程是否结束的标志。
    ②使用标准库里面自带的一个标志位

  3. interrupt方法的行为有两种情况:①t线程在运行状态会设置Thread.currentThread().isInterrupted() 为true
    (注:public static Thread currentThread();
    // 返回当前线程对象的引用,在哪个线程里调用得到的就是哪个线程的引用)
    ②t线程在阻塞状态(sleep)不会设置标志位,而是触发一个InterruptedException异常,这个异常会把sleep提前唤醒开始运行; 所以要想顺利结束运行就加一个break;

  4. 中断线程,目前常见的有以下两种方式:
    1) 通过共享的标记来进行沟通(也就是自定义标记)
    2) 调用 interrupt() 方法来通知

1)自定义标记:
需要给标志位上加 volatile 关键字(不加也是ok的)
如: public volatile boolean isQuit = false;

2)调用 interrupt() 方法来通知:
① 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位
【javaEE】多线程(初阶)Part2_第3张图片
② 使用 thread 对象的 interrupted() 方法通知线程结束

thread 收到通知的方式有两种:
① 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以InterruptedException 异常的形式通知,清除中断标志
当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法.。可以选择忽略这个异常, 也可以跳出循环结束线程。
② 否则,只是内部的一个中断标志被设置,thread 可以通过Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。

观察标志位是否清除
标志位是否清除, 就类似于一个开关.:
① Thread.isInterrupted() 相当于按下开关, 开关自动弹起来了. 这个称为 “清除标志位”
② Thread.currentThread().isInterrupted() 相当于按下开关之后, 开关弹不起来, 这个称为"不清除标志位".

  1. 在java中,中断线程并不是强制的,而是线程自身的代码来进行判定处理的。
    线程自身能怎么处理呢?
    ①立即结束线程; ②不理会; ③稍后理会 【也就是说取决于代码怎么写】

5. 线程阻塞

  1. 阻塞等待join:
    线程之间的调度顺序是不确定的,可以通过一些特殊的操作来对线程的执行顺序作出干预。如join就是一个方法来控制线程的结束顺序。
  2. 在main中调用t.join()效果就是:让main线程阻塞等待,等到t执行完了main才继续执行。
  3. 【java中的多线程方法,只要是这个方法会阻塞都可能抛出InterruptedException异常】
  4. 【如果是调用join之前t线程就已经结束了,此时main线程还需要等待吗?
    不需要!】
  5. join有带时间版本:等待,但并不是无限等待
    实际开发中,很少使用无线等待;大多数都指定了最大等待时间,避免程序因为死等导致“卡死”情况。
  6. join相关方法
    【javaEE】多线程(初阶)Part2_第4张图片

6. 线程休眠

  1. sleep指定休眠时间,让线程休息一会儿(阻塞一会儿)。
  2. 操作系统管理这些线程的PCB的时候是有多个链表的。
  • 就绪队列的PCB才会参与线程调度,而阻塞队列中的PCB暂时不会参与线程的调度。
  • 当就绪队列中的PCB sleep之后进入阻塞队列,当时间达到的时候该PCB又从阻塞队列挪回就绪队列,但是并不代表可以立即能够上CPU运行,还得看系统啥时候调度到该CPB。
  1. 所以:sleep(N); 并不一定是真的只休眠了N ms,一般是要略大于N的,具体时间看调度的时间开销。 因为:从阻塞到就绪之后不一定CPU就直接调度到该PCB!
  2. 【所以,为了解决上述调度的等待调度时间的不可预知性,就出现了“实时操作系统”,该操作系统的特点就是:任务调度的开销是可预期的,等待调度时间在可控范围内。
    (但是做到实时,是需要付出一些代价的) 如:知名实时操作系统:vxworks、wind river】
  3. sleep相关方法:(了解)
    【javaEE】多线程(初阶)Part2_第5张图片

二、线程状态

1.Java中有自带的状态,但是觉得不是特别合适;所以又自己搞了一套状态,具体如下:
① NEW:Thread 对象创建出来了,但是内核的PCB还没有创建,也就是说:还没有真正创建线程。
② TERMINATED:内核的PCB销毁了,但是Thread 对象还在
③ RUNNABLE:就绪状态(正在CPU上运行+在就绪队列中排队
④ TIMED_WAITING:按照一定的时间进行阻塞。 调用sleep、join这类带时间的都是TIMED_WAITING
⑤ WAITING:特殊的阻塞状态,调用wait等
⑥ BLOCKED:等待锁的时候进入的状态
(其实整体来说,状态就是:就绪+ 阻塞(分成三种具体情况了④⑤⑥))

2.状态转换:
【javaEE】多线程(初阶)Part2_第6张图片
3. 补充:
① RUNNABLE:(运行+排队)可被服务的状态,是否开始服务,则看调度器的调度
② isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的。
③ Thread. yield() 大公无私,让出 CPU。
yield 不改变线程的状态, 但是会重新去排队。


三、相关代码(看!)

Demo1-7


【补充】Java中获取随机数

注:随机数:【都是 前闭后开)
// Math.random() :生成[0,1)之间的浮点数!
// (int) Math.random() *100+1; [1,101)整数

// Random random = new Random();   int r = random.nextInt(随机种子); 
// 生成的是[0,随机种子)
// int r = random.nextInt() % 101;  // 这个会有负数存在!!!

THINK

  1. 线程默认是“前台线程”,只有前台线程结束进程才会结束
  2. run和start方法的区别
  3. 线程状态
  4. 代码书写

你可能感兴趣的:(Note-JavaEE,javaEE,线程)