目录
Thread 常见构造方法
Thread 常见属性
创建一个 Thread 线程
使用 jconsole 命令观察线程
中断一个 Thread 线程
等待一个 Thread 线程
休眠当前 Thread 线程
让出当前 Thread 线程的 CPU 资源
线程的状态
Thread 常见构造方法
方法 说明 Thread() 创建线程对象 Thread(Runnable target) 使用 Runnable 对象创建线程对象 Thread(String name) 创建线程对象并命名(可重名) Thread(Runnable target, String name) 使用 Runnable 对象创建线程对象并命名 Thread(ThreadGroup group,Runnable target) 线程可以被用来分组管理,分好的组即为线程组(了解即可) 注意:
- 当线程被创建时,如果程序员不主动为其命名,JVM 将默认命名为 thread-0、 thread-1、 thread-2 等
- 主动为线程命名最大的目的就是为了方便程序员调试
Thread 常见属性
属性 获取方法 解释 ID getId() 获取线程的 ID,为线程的唯一标识 名称 getName() 构造方法中起的名字 状态 getState() 获取线程的状态 优先级 getPriority() 获取线程的优先级,优先级可设置,但作用不大 是否后台线程 isDaemon() 代码中手动创建的线程和 main 默认为前台线程 内核线程是否存活 isAlive() Thread 对象 从调用 start 方法到线程 run 方法执行完,isAlive() 为 true 是否被中断 isInterrupted() 判断线程是否被中断 返回对象引用 currentThread() 返回当前线程对象的引用
创建一个 Thread 线程
可点击下方链接了解 7种创建线程 的方法
创建线程的七种方法
- 此处简单举例
//先创建一个类 让该类继承 Thread 父类 class MyThread extends Thread { // 重写 run 方法 @Override public void run() { System.out.println("在 run方法中 自定义线程的工作内容"); } } public class ThreadDemo1 { public static void main(String[] args) { // 创建实例t Thread t = new MyThread(); // 启动并创建新线程 t.start(); // 调用 run 方法仅执行 run 方法中的代码,并不会创建一个新线程 // t.run(); } }
注意:
- 仅调用 start 方法,系统内核才会真正创建一个线程
- 当 run 方法运行完,内核中的线程会销毁,但是 Thread 对象 t 还会存在
使用 jconsole 命令观察线程
- 我们需要借助 JDK,JDK 为 Java 开发工具安装包,其中包含很多工具
1.找到 jdk 安装目录并打开 jconsole
2.选择自己创建的程序并建立连接
- 如果打开该页面,本地进程中啥也没有,我们可以使用管理员方式运行 jconsole 程序
3.选择不安全连接
4.查看线程
测试代码:
class TestThread extends Thread { @Override public void run() { while (true) { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("这是 testThread "); } } } public class ThreadDemo9 { public static void main(String[] args) { Thread t = new TestThread(); t.start(); while (true) { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("这是 main"); } } }
中断一个 Thread 线程
- 中断的意思并不是让线程立即停止,而是通知线程,其应该要停止了,是否真的停止取决于线程中的具体代码
方案一
- 使用标志位来控制线程是否要停止
public class ThreadDemo10 { private static boolean flag = true; public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { while (flag) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); Thread.sleep(3000); // 主线程这里可以随时通过flag 变量的取值 来操作 t 线程是否结束 flag = false; } }
运行结果:
方案二
- 使用 Thread 自带的标志位来进行判定
- 因为使用方案一 自定义变量的方式时,不能即使响应,如果 run 方法中 sleep 休眠的时间比较长,则会大大影响体验感
- 使用 Thread 自带的标志位可以唤醒 sleep 方法
package Thread; public class ThreadDemo11 { 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) { e.printStackTrace(); } } }); t.start(); Thread.sleep(3000); // 哪个对象调用 interrupt 方法就是终止哪个线程 t.interrupt(); } }
运行结果:
- 该代码为线程t 忽略了中断请求
适当修改 run 方法内部代码
- 线程t 立即响应中断请求
- 线程t 等待一定时间后进行中断操作
- 当然也能加入其他代码,让线程 t 做点收尾工作再进行中断
总结:
- 中断的意思并不是让线程立即停止,而是通知线程,其应该要停止了,是否真的停止取决于线程中的具体代码
- sleep 之所以要清除标志位是因为当线程被唤醒后,该线程是否要中断,是立即中断还是稍后中断,由编写代码的程序员自己决定!
等待一个 Thread 线程
- 线程是随机进行调度的
- 等待线程 做的事就是控制两个线程的结束顺序
public class ThreadDemo12 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { for (int i = 0; i < 3; i++) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); System.out.println("join 之前"); // 此处的join 就是让当前的 main 线程来等待 t 线程执行结束(等待 t 的 run 方法执行完) t.join(); System.out.println("join 之后"); } }
运行结果:
注意:
- 如果执行 join 的时候,线程t 已经执行结束了,此时 mian线程 不会发生阻塞,会直接往下执行
- 相对于上述代码中 join 的无参版本为死等,join 还有两种带参数的版本更常用,为了防止死等带来程序卡死之类的情况
join(long millis) 指定一个最大等待时间 join(long millis, int nanos) nanos 参数表示要等待的额外纳秒数,精确度更高
休眠当前 Thread 线程
- 让线程休眠,本质上是让该线程不参与系统调度
public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis 毫秒 public static void sleep(long millis, int nanos) throws InterruptedException 提供更高精确度的休眠
- 新创建出来的线程一般是放在 就绪队列 中
- 当操作系统需要调度一个线程来执行时,就会从就绪队列中选一个
- 当线程调用 sleep 方法时,该线程就会从 就绪队列 进入阻塞队列,暂时不参与 CPU 的调度执行
- 但是线程的调度是不可控的,当 sleep 一定时间后线程重写回到就绪队列中时,不能保证其立即被 CPU 调度执行,所以该线程实际的休眠时间大于等于参数设置的休眠时间
让出当前 Thread 线程的 CPU 资源
- 通过 Thread.yield() 方法来暂停当前正在执行的线程(让出当前的 CPU 资源,并执行其他线程)
- yiled 方法不会改变线程的状态,只是让该线程从 在 CPU 中正在执行 转变为 回到就绪队列中重写排队参与调度
- 因为线程具有抢占式执行,随机调度的特点
- yield 方法无法完全保证当前线程能够让出 CPU 资源,因为可能刚进入就绪队列,又被CPU 调度执行了
线程的状态
- NEW:创建了 Thread 对象,但是还没调用 start 方法(内核中还未创建对应的 PCB)
- TERMINATED:表示内核中的 PCB 已经执行完毕,但是 Thread 对象还在,且一个线程只能 start 一次!
- RUNNABLE:表示可运行的(正在 CPU 上执行的 + 在就绪队列上随时可被调度的)
- WAITING:特殊的阻塞,调用 wait 方法
- TIMED_WAITING: 按照一定时间进行阻塞,调用 sleep 方法
- BLOCKED: 等待锁的时候进入阻塞状态