Java的线程支持提供了一些便捷的工具方法,通过这些便捷的工具方法可以很好地控制线程执行。

 

join线程

Thread提供了让一个线程等待另一个线程完成的方法——join()。当在某个线程执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。

join()方法通常由使用线程的程序调用,以将大问题划分为许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作。

public class JoinThread extends Thread {
	private int i;

	// 重写run方法,run方法的方法体就是线程执行体
	public void run() {
		for (; i < 100; i++) {
			System.out.println(getName() + " " + i);
		}
	}

	public static void main(String[] args) {
		// 创建线程对象
		StartDead sd = new StartDead();
		for (int i = 0; i < 300; i++) {
			// 调用Thread的currentThread方法获取当前线程
			System.out.println(Thread.currentThread().getName() + " " + i);
			if (i == 20) {
				// 启动线程
				sd.start();
				// 判断启动后线程的isAlive()值,输出true
				System.out.println(sd.isAlive());
			}
			// 只有当线程处于新建、死亡两种状态时isAlive()方法返回false。
			// 当i > 20,则该线程肯定已经启动过了,如果sd.isAlive()为假时,
			// 那只能是死亡状态了。
			if (i > 20 && !sd.isAlive())

			{
				// 试图再次启动该线程
				sd.start();
			}
		}
	}
}

join()方法有如下三种重载形式。

●join():等待被join的线程执行完成。

●join(long millis):等待被join的线程的时间最长为millis毫秒。如果在millis毫秒内被join的线程还没有执行结束,则不再等待。

●join(long millis, int nanos):等待被join的线程的时间最长为millis毫秒加nanos毫微秒。

 

后台线程

有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为"后台线程"(Daemon Thread),又称为"守护线程"或"精灵程序"。JVM的垃圾回收线程就是典型的后台线程。

后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。

调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程。下面程序将执行线程设置成后台线程,可以看到当所有的前台线程死亡时,后台线程随之死亡。当整个虚拟机中只剩下后台线程时,程序就没有继续运行的必要了,所以虚拟机也就退出了。

public class DaemonThread extends Thread {
	// 定义后台线程的线程执行体与普通线程没有任何区别
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println(getName() + "  " + i);
		}
	}

	public static void main(String[] args) {
		DaemonThread t = new DaemonThread();
		// 将此线程设置成后台线程
		t.setDaemon(true);
		// 启动后台线程
		t.start();
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "  " + i);
		}
		// -----程序执行到此处,前台线程(main线程)结束------
		// 后台线程也应该随之结束
	}
}

Thread类还提供一个isDaemon()方法,用于判断指定线程是否后台线程。

从上面线程可以看出,主线程默认是前台线程,t线程默认也是前台线程。并是不所有的线程默认都是前台线程,有些线程默认就是后台线程——前台想创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。

 

前台线程死亡后,JVM会通知后台线程死亡,但从它接收指令到做出响应,需要一定时间。而且要将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说,setDaemon(true)必须start()之前调用,否则会产生IllegalThreadStateException异常。

 

线程睡眠:sleep

如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法来实现。sleep()方法有两种重载形式。

●static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与标准度的影响。

●static void sleep(long millis, int nanos):让当前正在执行的线程暂停millis毫秒加nanos毫微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与标准度的影响。

当当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行。

下面程序调用sleep()方法来暂停主线程的执行,应为该线程只有一个主线程,当主线程进入睡眠后,系统没有可执行的线程,所以可以看到程序在sleep()方法处暂停。

public class SleepTest {
	public static void main(String[] args) throws Exception {
		for (int i = 0; i < 10; i++) {
			System.out.println("当前时间: " + new Date());
			// 调用sleep方法让当前线程暂停1s。
			Thread.sleep(1000);
		}
	}
}

 

线程让步:yield

yield()方法是一个和sleep()方法有点相似的方法,它可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。yield()只是让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。

实际上,当某个线程调用了yield()暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。下面程序使用yield()方法来让当前正在执行的线程暂停。

public class YieldTest extends Thread {
	public YieldTest(String name) {
		super(name);
	}

	// 定义run方法作为线程执行体
	public void run() {
		for (int i = 0; i < 50; i++) {
			System.out.println(getName() + "  " + i);
			// 当i等于20时,使用yield方法让当前线程让步
			if (i == 20) {
				Thread.yield();
			}
		}
	}

	public static void main(String[] args) throws Exception {
		// 启动两条并发线程
		YieldTest yt1 = new YieldTest("高级");
		// 将ty1线程设置成最高优先级
		yt1.setPriority(Thread.MAX_PRIORITY);
		yt1.start();
		YieldTest yt2 = new YieldTest("低级");
		// 将yt2线程设置成最低优先级
		yt2.setPriority(Thread.MIN_PRIORITY);
		yt2.start();
	}
}

关于sleep()和yield()方法的区别如下。

●sleep()暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield()方法只会给优先级相同,或优先级更高的线程执行机会。

●sleep()方法会将线程转入就绪状态;而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。一次完全有可能某个线程调用了yield()方法暂停之后,立即再次获得处理器资源被执行。

●sleep()方法声明抛出了IntetuptedException异常,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;而yield()方法则没有抛出任何异常。

●sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()来控制并发线程的执行。

 

改变线程优先级

每个线程执行时具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会。

每个线程默认的优先级都与创建它的父线程的优先级相同,在默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级。

Thread类提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,其中setPriority()方法的参数可以是一个整数,范围是1~10之间,也可以使用Thread类的如下三个静态常量。

●MAX_PRIORITY:其值是10。

●MIN_PRIORITY:其值是1。

●NORM_PRIORITY:其值是5。

下面程序使用了setPriority()方法来改变主线程的优先级,并使用该方法改变了两个线程的优先级,从而可以看到优先级的线程将会获得更多的执行机会。

public class PriorityTest extends Thread {
	// 定义一个有参数的构造器,用于创建线程时指定name
	public PriorityTest(String name) {
		super(name);
	}

	public void run() {
		for (int i = 0; i < 50; i++) {
			System.out.println(getName() + ",其优先级是:" + getPriority() + ",循环变量的值为:" + i);
		}
	}

	public static void main(String[] args) {
		// 改变主线程的优先级
		Thread.currentThread().setPriority(6);
		for (int i = 0; i < 30; i++) {
			if (i == 10) {
				PriorityTest low = new PriorityTest("低级");
				low.start();
				System.out.println("创建之初的优先级:" + low.getPriority());
				// 设置该线程为最低优先级
				low.setPriority(Thread.MIN_PRIORITY);
			}
			if (i == 20) {
				PriorityTest high = new PriorityTest("高级");
				high.start();
				System.out.println("创建之初的优先级:" + high.getPriority());
				// 设置该线程为最高优先级
				high.setPriority(Thread.MAX_PRIORITY);
			}
		}
	}
}