上篇文章【JavaEE】操作系统+进程+线程_p_fly的博客-优快云博客 讲了线程的概念,接下来就介绍如何创建线程和相关的注意事项。
目录
创建线程的五种常用写法
在Java中,操作多线程最核心的类就是Thread,它是java.lang下的,所以使用的时候也不需要导入包。接下来创建的五种写法就是围绕着Thread类来写的。
1.继承Thread类
// 这里的Test1类继承了Thread类,重写了其run方法
class Test1 extends Thread {
@Override
public void run() {
System.out.println("这是t1线程的任务!");
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Test1 t1 = new Test1();
t1.start();
System.out.println("这是main(主)线程的任务!");
}
}
2.使用匿名内部类,继承Thread类
// 使用匿名内部类继承Thread
// 本质上与第一种方法一样
public class ThreadDemo2 {
public static void main(String[] args) {
Thread t2 = new Thread(new Thread() {
@Override
public void run() {
System.out.println("这是t2线程的任务!");
}
});
t2.start();
System.out.println("这是主线程的任务!");
}
}
3.实现Runnable接口
// 这里的Test3类实现了接口的
// 使用接口的话,可以把线程和线程的任务更好的分开,如果以后想要该代码,改动量就不会很大
class Test3 implements Runnable {
@Override
public void run() {
System.out.println("这是t3的任务!");
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
Runnable runnable = new Test3();
Thread t3 = new Thread(runnable);
t3.start();
System.out.println("这是主线程的任务!");
}
}
4.使用匿名内部类,实现Runnable接口
// 使用匿名内部类,实现Runnable接口
// 本质上与第三种方法一样
public class ThreadDemo4 {
public static void main(String[] args) {
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("这是t4的任务!");
}
});
t4.start();
System.out.println("这是主线程的任务!");
}
}
5.Lambda表达式
// 使用Lambda表达式
// 这是最简洁也是最推荐的写法
public class ThreadDemo5 {
public static void main(String[] args) {
Thread t5 = new Thread(() -> {
System.out.println("这是t5的任务!");
});
t5.start();
System.out.println("这是主线程的任务!");
}
}
Thread类中的部分方法
Thread类中的方法有很多,这里只介绍一些常用的方法。
静态方法(类方法)
这里只介绍常用的静态方法.
获取线程实例
休眠线程
方法 | 说明 |
Thread.currentThread() | 返回当前线程正在执行的线程的引用 一般获取到线程的引用, 就可以进行后续的操作 |
Thread.sleep(long millis) | 返回void 让当前正在执行的进程休眠 millis 毫秒 sleep方法必须要处理异常 所以必须声明异常或用try catch处理异常 当线程被休眠后, 本质上该线程的PCB就到了一个阻塞队列(一段时间内无法被CPU调度)中 当线程休眠结束后, PCB就到了就绪队列(可以随时被CPU调度)当中去 所以, 线程休眠结束后, 不一定就马上被PCB调度执行, 只是它可以被调度了, 具体还要看CPU什么时候调度该线程 |
Thread.sleep(long millis, int nanos ) | 返回void 让当前执行的进程休眠 millis毫秒 + nanos 纳秒 (用于高精度的场景) |
构造方法
这里只介绍常用的构造方法.
方法 | 说明 |
public Thread() | 创建一个线程对象,自动生成名字为Thread-n,n是整数 |
public Thread(String name) | 创建一个线程对象,并命名 name |
public Thread(Runnable target) | 使用Runnable对象创建一个线程对象,自动生成名字Thread-n |
public Thread(Runnable target, String name) | 上面组合一下 |
实例方法
获取Thread的常见属性
这里只介绍常用的几个属性
属性 | 方法 | 说明 |
ID | int getId() | 获取线程的ID, ID是独一无二的,每个线程ID都不一样 |
名称 | String getName() | 获取当前线程的名称,调试时用 |
状态 | Thread.State getState() | 获取当前线程的状态,下篇文章详解 |
优先级 | int getPriority() | 获取线程的优先级, 一般优先级越高, 越容易被CPU调度到 |
是否为守护线程(后台线程) | boolean isDaemon() | 返回值为true -- 是守护线程 false -- 非守护线程 一个进程中, 所有的非守护线程结束后, 进程才会结束, 否则就不会结束, 如果此时守护线程还没有结束, 那么也会结束掉 |
是否存活 | boolean isAlive() | true -- 已启动, 为死亡 false -- 未启动或者已经死亡 简单理解为如果run()方法执行完毕, 线程就寄了 |
是否被中断 | boolean isInterrupted() | true -- 已被中断 false -- 未被中断 判断线程是否被中断(阻塞), 下面详解 |
描述线程的任务
方法 | 说明 |
void run() | 这个方法仅仅只是描述了改线程的任务, 要干那哪些活, 不是代表着创建了一个线程, 不是CPU开始调度改线程. 如果仅仅只是单独调用改方法, 就没有创建出线程来, 只是相当于调用一个类的普通方法而已. |
启动(创建)线程
方法 | 说明 |
void start() | 调用这个方法, 线程才会真正的在操作系统底层创建出来 |
等待线程
方法 | 说明 |
void join() | 在a线程中, b线程调用这个方法, 那么b线程就继续执行, 而a线程就得等b线程结束后才能继续工作(a线程阻塞了) 总结一下就是谁调用该方法, 谁就不受影响 |
void join(long millis) | 等待线程结束后, 最多在等millis毫秒 |
void join(long millis, int nanos) | 精确到纳秒了 |
// 这里是在mian线程中, t7线程调用了join方法
// 这样主线程就得等t7任务完成后才能继续工作
public class ThreadDemo7 {
public static void main(String[] args) throws InterruptedException {
Thread t7 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("这是t7线程的任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t7.start();
t7.join();
for (int i = 0; i < 5; i++) {
System.out.println("这是main线程的任务!");
Thread.sleep(1000);
}
}
}
没有等待的效果
等待的效果
中断线程
中断的本质是线程中中断标志有无. Thread内部中有一个boolean类型的变量, 这个变量是判断线程是否被中断标记. 这个标志我们可以人为创造一个类似的, 也可以用Thread带的方法操作现有的.
中断线程并不是立刻让一个线程进入阻塞状态, 相当于只是通知了线程, 具体是 立刻中断, 还是过一会中断, 还是不管中断的通知, 全都取决于线程内部的代码是如何实现的.
1. 通过公共标记中断线程
这种自定义变量的方法不能及时响应. 代码中解释了
public class ThreadDemo8 {
// 这就是我们人为创建的标志位来控制线程的中断
public static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread t8 = new Thread(() -> {
while (flag) {
System.out.println("这是t8线程的任务! ");
try {
// 如果该线程中有sleep, wait, join等让线程阻塞的操作
// 并且在修改flag 成为false时, 该线程恰好被阻塞了
// 此时只能等阻塞时间结束, 才可以终止线程
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t8.start();
System.out.println("这是主线程的任务! 主线程休眠 500ms后将会通知t8线程中断");
Thread.sleep(500);
flag = false;
}
}
2. 通过调用interrupt()方法中断线程
方法 | 说明 |
boolean isInterrupted() | 线程调用这个方法就返回改线程的标志位情况 true -- 被终止 false -- 为被终止 |
void interrupt() | 哪个线程调用改方法, 哪个线程就中断 但是线程真正的中断与否还是取决于代码实现 该方法不会等待线程中的sleep, join等能够让线程阻塞的方法. 它会直接触发这些方法的异常 |
上面这两种方法可以简单的理解为把我们人为的标志给封装到方法当中了.
public class ThreadDemo10 {
public static void main(String[] args) throws InterruptedException {
Thread t10a = new Thread(() -> {
// 获取到t10a线程的引用, 调用isInterred方法判断该线程的中断状态
while (!Thread.currentThread().isInterrupted()) {
System.out.println("这是t10a线程的任务! ");
try {
// sleep等带有阻塞效果的方法被触发异常后会把 标志位再变成false
// 这样这个线程还可以继续执行
// 这也就是通知了中断, 但是线程忽略 的效果
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t10b = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("这是t10b线程的任务! ");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
// 这里触发了异常后 break 跳出循环, 相当于结束了这个线程
// 相当于通知中断线程后立马相应结束
break;
}
}
});
Thread t10c = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("这是t10c线程的任务! ");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
// 这里相当于收到中断的通知后, 再执行一会任务才中断
System.out.println("等会结束t10c线程的任务!");
break;
}
}
});
t10a.start();
//t10b.start();
//t10c.start();
System.out.println("这是主线程任务! 3000ms后通知终止 t10a / t10b / t10c 线程! ");
Thread.sleep(3000);
t10a.interrupt();
//t10b.interrupt();
//t10c.interrupt();
}
}
t10a线程效果——通知中断但忽略
t10b线程效果——通知中断就立刻中断
t10c线程效果——通知中断但过一会中断
这三种不同处理结果取决于程序员如何写代码, 这样也就给了程序员更多的选择权.
希望可以帮到你, 有什么错误评论区指出.