一、join线程
Thread类提供了让一个线程等待另一个线程完成的方法——join()方法。当线程A执行时,调用另一个处于就绪状态的线程B的join()方法,线程A会被阻塞,直到线程B执行完为止。join()方法通常由使用线程的程序调用,以将大问题划分为许多小问题,每个小问题分配一个线程,当所有小问题都得到处理后,再调用主线程进一步操作。
下面是代码示例:
public class ThreadJoin extends Thread {
public static void main(String[] args) throws InterruptedException {
//把主线程的名字改为线程A
Thread.currentThread().setName("线程A");
//创建线程C,和线程A并发执行
new ThreadJoin("线程C").start();
for(int i = 0;i < 10;i++) {
if(i == 5) {
//创建线程B
ThreadJoin thread_B = new ThreadJoin("线程B");
thread_B.start();
/*
* 线程A在i等于5的时候创建并启动线程B,并且调用了线程B的join()方法,
* 调用了该方法以后,线程A必须等到线程B执行完成以后才会向下执行
*/
thread_B.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public ThreadJoin(String name) {
super(name);
}
public void run() {
for(int i = 0;i < 5;i++) {
System.out.println(this.getName() + " " + i);
}
}
}
代码执行结果如下图:

从执行结果可知:线程A和线程C并发执行,当 i = 5 时,创建、启动线程B,并且调用线程B的join()方法。调用了线程B的join()方法后,线程A就被阻塞了,直到线程B执行完成以后,线程A从阻塞状态变为就绪状态,才有机会抢占CPU重新进入运行状态。
注:为了截图方便,我把 i 的值设置的比较小,不能直观的看出当线程A被阻塞以后,线程B和线程C并发执行的情况,以及线程B执行完成后,线程A又和线程C并发执行的情况。
join()方法有以下三种重载形式:
1、join():等待被join的线程执行完成;
2、join(long millis):等待被join的线程的最长时间为millis毫秒。如果在millis毫秒内被join的线程还未执行完成,则不再等待;
3、join(long millis,int nanos):等待被join的线程的最长时间为millis毫秒加nanos毫微秒。
通常很少使用第三种形式,因为:程序对时间的精度无须精确到毫微秒,以及计算机硬件、操作系统本身也无法精确到毫微秒。
二、后台线程
有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为“后台线程”,又称为“守护线程”或“精灵线程”。JVM的垃圾回收线程就是典型的后台线程。后台线程的特征:如果所有前台线程都死亡,后台线程会自动死亡。调用Thread对象的setDaemon(true)方法可将指定线程设置为后台线程。
代码示例:
public class DaemonThread extends Thread {
public void run() {
for(int i = 0;i < 100; i++) {
System.out.println(this.getName() + " " + i);
}
}
public static void main(String[] args) {
DaemonThread daemonThread = new DaemonThread();
daemonThread.setName("后台线程");
daemonThread.setDaemon(true);
daemonThread.start();
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
//程序执行到此处main线程结束,因为本程序只有main线程这一个前台线程,所以main线程死亡,所有后台线程也死亡
}
}
执行结果:


执行结果分析:
当main线程执行结束后,意味着所有前台线程死亡。此时,JVM会通知后台线程死亡,但从它接收到指令到做出响应需要一段时间,所以在执行结果中可以看到:main线程执行结束后,后台线程还运行了一段时间(输出main 9以后程序还在执行)。
注意:
①前台线程创建的子线程默认是前台前程,后台线程创建的子线程默认是后台线程;
②setDaemon(true)方法必须在调用start()方法前调用,否则会引发异常。
三、线程睡眠
如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,可以通过调用Thead类的静态方法sleep()方法来实现。当线程调用sleep()方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他处于就绪状态的线程,处于sleep()中的线程也不会执行,因此sleep方法常用来暂停程序的执行。
代码示例:
import java.util.Date;
public class SleepThread {
public static void main(String[] args) throws Exception {
for(int i = 0;i < 10;i++) {
System.out.println("当前时间:" + new Date());
Thread.sleep(1000);
}
}
}
执行结果:

结果分析:
当前测试环境只有一个前台线程main线程,每两条输出字符串之间间隔1s,当main线程调用sleep()阻塞时,在设定的睡眠时间内,main线程没有获得CPU。
yield()方法:
yield()方法也可以让当前正在执行的线程暂停,但该方法不会阻塞线程,而是让线程让出CPU,并回到就绪状态。所以可能会出现这种情况:当某个线程调用了yield()方法暂停以后,线程调度器又将该线程调度起来运行。
实际上,当某个线程调用yield()方法暂停以后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。
yield()方法和sleep()方法的区别:
①sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级。但yield()方法只会给优先级相同,或优先级更高的线程执行机会;
②sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态。而yield()方法不会将线程转入阻塞状态,而是强制把当前线程从运行状态转到就绪状态;
③sleep()方法声明抛出InterruptedException异常,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常。而yield()方法则没有声明抛出任何异常;
④sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。
四、改变线程优先级
每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会。每个线程默认的优先级都与创建它的父线程的优先级相同。Thread类提供了setPriority(int newPriority)来设置指定线程的优先级,提供了getPriority()来返回指定线程的优先级。
其中,setPriority(int newPriority)方法的参数可以是一个整数,范围1~10之间,也可以使用Thread的三个静态常量:MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY,这三个静态常量所代表的值分别为:10、1、5。
注意:
虽然JAVA提供了10个优先级级别,但这些优先级别级别需要操作系统支持。遗憾的是,不同的操作系统上的优先级并不相同,而且也不能很好的和JAVA的10个优先级对应,比如:Windows 2000仅提供了7个优先级。因此,我们写代码的时候应该尽量避免直接为线程指定优先级,而应该使用MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY这三个静态常量来设置优先级,这样才能保证程序有最好的可移植性。
本文详细介绍了JAVA中控制线程的方法,包括join线程的原理和三种重载形式,后台线程的定义及转换,线程睡眠的实现与yield()方法的区别,以及如何改变线程的优先级。通过实例代码和执行结果分析,帮助理解JAVA线程管理的关键概念。
701

被折叠的 条评论
为什么被折叠?



