Java 控制线程
前言
该篇讲解Java如何对线程进行状态控制,比如:如何将一个线程从一个状态转到另一个状态,如何设置线程的优先级等。
join() 等待阻塞
让一个线程等待另一个线程完成才继续执行。如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,直到B线程执行完为止,A才能得以继续执行。
join()方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作。
/**
* @ClassName: JoinThread
* @Description: join线程
* @author: 陈琼
* @connect:907172474@qq.com
* @date: 2021年2月13日 上午11:34:42
*/
public class JoinThread extends Thread {
public JoinThread(String name) {
super(name);
}
public void run() {
for (var i = 0; i < 20; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) throws InterruptedException {
new JoinThread("新线程").start();
for (var i = 0; i < 20; i++) {
if (i == 5) {
var jt = new JoinThread("被Join的线程");
jt.start();
jt.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
运行结果:
main 0
main 1
main 2
main 3
main 4
新线程 0
新线程 1
新线程 2
新线程 3
被Join的线程 0
新线程 4
新线程 5
被Join的线程 1
新线程 6
新线程 7
被Join的线程 2
新线程 8
新线程 9
被Join的线程 3
新线程 10
新线程 11
新线程 12
新线程 13
新线程 14
被Join的线程 4
新线程 15
新线程 16
被Join的线程 5
新线程 17
新线程 18
被Join的线程 6
新线程 19
被Join的线程 7
被Join的线程 8
被Join的线程 9
被Join的线程 10
被Join的线程 11
被Join的线程 12
被Join的线程 13
被Join的线程 14
被Join的线程 15
被Join的线程 16
被Join的线程 17
被Join的线程 18
被Join的线程 19
main 5
main 6
main 7
main 8
main 9
main 10
main 11
main 12
main 13
main 14
main 15
main 16
main 17
main 18
main 19
join()方法有如下三种重载形式:
- join():等待被join的线程执行完成。
- join(long millis):等待被join的线程的时间最长为millis毫秒。如果在millis毫秒内被join的线程还没执行结束,则不再等了。
- join(long millis, int nanos):等待被join的线程的最长时间为millis毫秒加nanos毫微秒(这种方式很少用)。
后台线程
有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为“后台线程(Daemon Thread)”,又称为“守护线程”或“精灵线程”。JVM的垃圾回收线程就是典型的后台线程。
后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。
调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程。下面程序将执行线程设置成后台线程,可以看到当所有前台线程死亡时,后台线程随之死亡。当整个虚拟机只剩下后台线程时,程序就没有继续运行的必要了,所以虚拟机也就退出了。
/**
* @ClassName: DaemonThread
* @Description: 后台线程
* @author: 陈琼
* @connect:907172474@qq.com
* @date: 2021年2月13日 上午11:59:09
*/
public class DaemonThread extends Thread {
public void run() {
for (var i = 0; i < 100; i++) {
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args) {
var t = new DaemonThread();
t.setDaemon(true);
t.start();
for (var i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
执行结果
main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
Thread-0 4
main 8
Thread-0 5
Thread-0 6
main 9
Thread-0 7
Thread-0 8
Thread-0 9
Thread-0 10
Thread-0 11
Thread-0 12
Thread-0 13
Thread-0 14
Thread-0 15
Thread-0 16
Thread-0 17
Thread-0 18
Thread-0 19
Thread-0 20
Thread-0 21
Thread-0 22
Thread-0 23
Thread-0 24
Thread-0 25
Thread-0 26
说明
前台线程死亡后,JVM会通知后台线程死亡,但从它接受指令到做出响应,需要一定时间。而且要将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说,setDaemon(true)必须在start()方法之前调用,否则会引发IllegalThreadStateException异常。
线程睡眠:sleep
如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法来实现。
当当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行。
/**
* @ClassName: SleepTest
* @Description: 线程sleep
* @author: 陈琼
* @connect:907172474@qq.com
* @date: 2021年2月13日 下午12:32:46
*/
public class SleepTest {
public static void main(String[] args) throws InterruptedException {
for (var i = 0; i < 10; i++) {
System.out.println("当前时间:" + new Date());
Thread.sleep(1000);
}
}
}
执行结果:
当前时间:Sun Apr 11 12:32:28 CST 2021
当前时间:Sun Apr 11 12:32:29 CST 2021
当前时间:Sun Apr 11 12:32:30 CST 2021
当前时间:Sun Apr 11 12:32:31 CST 2021
当前时间:Sun Apr 11 12:32:32 CST 2021
当前时间:Sun Apr 11 12:32:33 CST 2021
当前时间:Sun Apr 11 12:32:34 CST 2021
当前时间:Sun Apr 11 12:32:35 CST 2021
当前时间:Sun Apr 11 12:32:36 CST 2021
当前时间:Sun Apr 11 12:32:37 CST 2021
线程让步:yield()
yield将线程从运行状态转换为就绪状态,当某个线程调用 yiled() 方法从运行状态转换到就绪状态后,CPU会从就绪状态线程队列中选择与该线程优先级相同或优先级更高的线程去执行。
使用时直接用 Thread.yield() 静态方法就可以了。一般情况下我们的 CPU 都很难达到100% 的利用率,所以当我们使用yield()方法将线程挂起之后,一般又会立即获得资源,继续执行,因此很难写个程序去进行验证。而且这个方法在工作中也是比较少用到,所以只需要了解其作用就可以了。
sleep() 和 yield() 两者的区别:
- sleep()方法会给其他线程运行的机会,不考虑其他线程的优先级,因此会给较低优先级线程一个运行的机会。yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。
- 当线程执行了sleep(long millis)方法,将转到阻塞状态,参数millis指定睡眠时间。当线程执行了yield()方法,将转到就绪状态。
- sleep()方法声明抛出InterruptedException异常,而 yield() 方法没有声明抛出任何异常。
- sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。
改变线程优先级
每个线程在执行时都具有一定的优先级,优先级高的线程具有较多的执行机会。每个线程默认的优先级都与创建它的线程的优先级相同。main线程默认具有普通优先级。
设置线程优先级:setPriority(int priorityLevel),参数priorityLevel范围在1~10之间,常用的有如下三个静态常量值:
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5
获取线程优先级:getPriority()。
注意:具有较高线程优先级的线程对象仅表示此线程具有较多的执行机会,而非优先执行。
/**
* @ClassName: PriorityTest
* @Description: 线程优先级
* @author: 陈琼
* @connect:907172474@qq.com
* @date: 2021年2月13日 下午12:45:48
*/
public class PriorityTest extends Thread {
public PriorityTest(String name) {
super(name);
}
public void run() {
for (var i = 0; i < 50; i++) {
System.out.println(getName() + ",其优先级是:" + getPriority() + ",循环变量的值为:" + 1);
}
}
public static void main(String[] args) {
Thread.currentThread().setPriority(6);
for (var i = 0; i < 30; i++) {
if (i == 10) {
var low = new PriorityTest("低级");
low.start();
System.out.println("创建之初的优先级:" + low.getPriority());
low.setPriority(Thread.MIN_PRIORITY);
}
if (i == 20) {
var high = new PriorityTest("高级");
high.start();
System.out.println("创建之初的优先级:" + high.getPriority());
high.setPriority(Thread.MAX_PRIORITY);
}
}
}
}
运行结果
创建之初的优先级:6
低级,其优先级是:6,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
创建之初的优先级:6
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
低级,其优先级是:1,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
高级,其优先级是:10,循环变量的值为:1
值得指出的是,虽然Java提供了10个优先级别,但这些优先级别需要操作系统的支持。遗憾的是,不同操作系统上的优先级并不相同,而且也不能很好地和Java的10个优先级对应。因此应该尽量避免直接为线程指定优先级,而应该使用 MAX_PRIORITY,MIN_PRIORITY,NORM_PRIORITY三个静态常量来设置优先级,这样才可以保证程序具有最好的可移植性。