Thread类及线程的核心操作
一. Thread类的常见构造方法
1. Thread()
Thread类无参的构造方法, 用于创建Thread类的实例对象.
2. Thread(String name)
带一个参数的Thread类构造方法, 创建一个线程对象, 并给其命名.
[注]: 如果不专门给线程命名, 那么线程默认的名字就是Thread-0, Thread-1, Thread-2 ...... , 给线程取名字, 不会影响到线程的执行效果, 为线程取一个合适的名字, 有利于调试程序.
3. Thread(Runnable target)
带一个参数的Thread类构造方法, 使用Runnable对象来创建线程对象.
4. Thread(Runnable target, String name)
带两个参数的Thread类构造方法, 使用Runnable对象来创建一个线程对象, 并给其命名.
5. Thread(ThreadGroup group, Runnable target)
[了解] 带两个参数的Thread类构造方法, 使用Runnable对象来创建线程, 并将线程分成线程组. 设置线程组是为了方便统一设置线程的一些属性, 现在实际开发中很少使用线程组, 而是使用"线程池". 所以, 该构造方法我们了解即可.
二. Thread类几个常见属性和方法
1. Id
ID是Thread对象的身份标识, 是JVM自动分配的, 不能手动设置. 是Thread对象的身份标识. 使用getId()方法可以获取Thread对象的ID.
[注意]: ID是Thread对象的身份标识, 而不是线程的身份标识!!! 通常情况下, 一个Thread对象应该是对应到系统内核的一个线程上的, 但是也有可能Thread对象创建好了, 却没有启动这个线程, 那么此时系统内核就没有这个线程. 或者线程已经销毁了, 但是Thread对象还在.
2. name
线程名称, 使用getName()方法可以获取线程的name.
3. state
线程状态, 是JVM自动分配的, 不能手动设置. "就绪/阻塞 ... 都是进程的状态".
4. priority
进程优先级.
5. isDaemon()
是否为后台线程. (一个线程如果没有指定, 那么默认它是前台线程)
这里我们要辨析一下, 什么是"后台线程", 什么是"前台线程", 以及这两者之间有什么区别.
(1) 前台线程
概念: 如果某线程在执行过程中能够控制线程结束, 那么这个线程就是前台线程.
特点: 前台线程宣布进程结束, 此时进程立即结束, 后台线程也会随之结束. 前台线程没有宣布结束, 后台线程即使结束了也不影响进程继续执行.
(2) 后台线程
概念: 如果某个线程在执行过程中不能控制线程结束, 那么这个线程就是后台线程. (例如: 虽然某个后台线程正在执行, 但是此时进程要结束了, 那么后台线程也会随之结束.)
特点: 进程要结束(前台线程要结束), 后台线程无力阻止, 也会随之结束. 后台线程先结束了, 也不影响进程的继续执行(其他前台线程的继续执行).
通过上述分析, 我们可以理解为, 前台线程就是"话事人", 而后台线程就是一个"小透明". 前台线程的一举一动都影响着整个进程的状态, 而后台线程是否结束则对整个进程丝毫没有影响.
6. isAlive()
是否存活.
这里isAlive()表示的是内核中的线程是否存活. 为true表示内核中的线程存在, 为false表示内核中的线程不存在了.
7. isInterrupted()
线程是否被中断.
我们可以通过代码演示一下上述几个属性方法.
public class Demo4 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 3; i++){
System.out.println("hello thread");
}
}
});
System.out.println(t.getId());//获取线程ID
System.out.println(t.getName());//获取线程名称
System.out.println(t.getState());//获取线程状态
System.out.println(t.getPriority());//获取线程优先级
System.out.println(t.isDaemon());//是否为后台线程
System.out.println(t.isAlive());//是否存活
System.out.println(t.isInterrupted());//是否被终止
t.start();
}
}
代码运行结果如下:
三. 线程的几个核心操作
1. 启动一个线程 -- start()
start() 方法由一个线程对象调用, 表示启动该线程. 一旦start()方法执行完毕, 新线程就会立即开始执行. 调用start() 的线程也会继续执行.
例如: 我们在main方法中用t调用了start()方法, 相当于启动了一个新线程t, 从此刻开始, 线程"兵分两路", 一路继续执行main线程, 一路执行新的t线程, 两个线程并发执行. 示意图如下:
[注意]: 由于一个Thread对象只能对应内核中的一个线程, 所以一个Thread对象只能调用一次start()方法(只能启动一次线程). 如果多次调用start()方法, 那么就会报错.
2. 终止一个线程
例如, 我们现在有A, B两个线程. B正在运行, 而A想要B结束. 此时A要做的就是让B把它的run方法执行完, B线程自然就结束了. (注: 这里A不会直接强制终止B)
我们举如下示例: 设置一个isRunning变量. isRunning为true, 则表示t线程在运行; isRunning为false, 则表明线程结束.
public class Demo7 {
private static boolean isRunning = true;
public static void main(String[] args) {
// boolean isRunning = true;
Thread t = new Thread(() -> {
while (isRunning) { // 如果t线程运行, 则执行while中的任务
System.out.println("hello thread");
try {
Thread.sleep(1000); // 每隔1000ms打印一次"hello thread"
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t 线程结束");
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 3s 之后, 主线程修改 isRunning 的值, 从而通知 t 结束.
System.out.println("控制 t 线程结束");
isRunning = false; //将isRunning置为false, 表示控制t线程结束.
}
}
这段代码运行结果如下:
当然, 由于线程的终止是一个"比较温和"的操作, 所以, 当A线程让B线程结束时, B可以自行选择: 立即结束 / 执行完当前任务再结束 / 无视A, 继续执行.
public class Demo8 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
// t.isInterrupted();
Thread currentThread = Thread.currentThread(); //currentThread是Thread类中的一个静态方法, 表示获取当前线程的引用
while (!currentThread.isInterrupted()) { //isInterrupted是Thread类中的一个成员方法,用于检查线程是否已被中断.初始值是false, 表示线程没有被中断.
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// throw new RuntimeException(e);
// e.printStackTrace();
// balabala 写一些其他的逻辑之后, 再 break
//break;
}
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t.interrupt();
}
}
interrupt方法, 能够设置标志位, 也能唤醒sleep等阻塞方法; sleep方法被唤醒后, 又能清除标志位.
上述代码中, 我们通过在catch中进行不同的操作, 可以实现不同的目的.
(1) 无视A的命令, 继续执行: catch中什么都不做.
(2) 稍后结束线程: 线执行一系列收尾工作,在break/return.
(3) 立即结束线程: 直接break/return.
3. 等待一个线程 -- join
我们知道, 多个线程的并发执行, 实质上是一个"随机调度, 抢占式执行"的方式. 所以我们程序员并不能指定哪个线程先执行, 哪个线程后执行. 但是我们可以通过让一个线程等待另一个线程来控制哪个线程先结束, 哪个线程后结束. 这里的线程等待, 就要用到 join方法.
例如: 现在计算机上正在执行两个线程a和b, 我们现在想要让a线程等待b线程执行完, a才能继续维往下执行.
基本语法是:
b.join ();
意思就是让a线程等待b线程执行结束, a再继续往下执行.
public class Demo10 {
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();
}
}
System.out.println("thread end");
});
t.start();//启动t线程
for (int i = 0; i < 5; i++) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("开始等待t线程");
t.join(); // 此处在main线程中加一个t.join(), 表示main线程等待t线程执行结束后才能结束
System.out.println("t线程执行结束, 等待完成");
System.out.println("继续执行main线程后面的部分");
System.out.println("main线程执行结束");
}
}
此处在main线程中加一个t.join(), 就表示main线程等待t线程执行完成之后继续才能执行main线程后面的任务.
那么我们可不可以让t线程等待main线程呢? 那当然也是可以的~
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread t = new Thread(() -> {
System.out.println("t线程开始等待");
try {
mainThread.join(); //在t线程中插入一个mainThread.join(), 表示让t线程等待main线程.
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("main线程执行结束, t线程结束等待, 准备开始执行t线程后面的内容");
for (int i = 0; i < 3; i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t线程执行结束");
});
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("hello main");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main线程执行结束");
}
}
那么此时的t线程就必须等main线程的任务全部执行完才能执行t线程后面的任务了. 上述代码执行结果如下:
好, 既然两个线程之间可以相互等待, 那么一个线程能不能等待多个线程呢? 答案也是可以的~~
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(() -> {
for (int i = 0; i < 4; i++) {
System.out.println("t2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t1 = new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("t1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
System.out.println("开始等待t1, t2");
t1.join();
t2.join();
System.out.println("t1, t2执行完毕, main线程结束等待t1, t2");
System.out.println("继续执行main后面的任务");
System.out.println("main线程执行结束");
}
}
上述代码执行结果如下:
注意: join()的作用是 保证被等待的线程能够先结束. 如果开始等待的时候, 发现被等待的线程已经结束了, 那么就不需要再等了.
上述使用的join( ) 不带参数, 表示"死等", 只要被等待的线程不执行完毕, 就会持续阻塞, 不会继续往下执行. 但是这样的等待方式显然是不好的, 所以java还提供了一种带参数的形式: join( long millis) 参数表示最多等待millis毫秒, 如果在这段时间内等待还没有结束, 那么就不会再等了.
4. 获取当前线程的引用
使用Thread类的currentThread()方法, 表示获取当前线程的引用.
例如, 获取main线程的引用:
在任何线程中, 在任何需要的时候, 都可以通过此方法, 拿到当前线程的引用.
5. 休眠当前线程
通过Thread类中的sleep()方法, 休眠当前线程. 某线程执行sleep(), 就会让该线程处于阻塞等待状态, 此时这个线程不参与CPU调度, 从而把CPU资源让出来, 给别人使用.
使用sleep()可以解决实际开发中某些线程CPU占用率过高的问题.
好的, 以上就是本篇博客的全部内容啦, 如果喜欢小编的文章, 可以点赞,评论,收藏~