多线程运作就像是多马奔跑,它们的运作是有目的的,是需要完成特定任务的,而不是信马由缰乱跑,需要我们对线程控制,下面就对线程控制的类型和方法进行详细介绍。
0.知识体系
1.线程等待---join
join()方法是让一个线程等待另一个线程完成的方法,由Thread提供。当在某个程序执行流中调用其他线程的join方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。
join()方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程当所有小问题都得到处理后,再调用主线程进一步操作。
join()方法3种重载形式:
1)join():等待被join的线程执行完成。
2)join(long millis):等待被join的线程的时间最长为millis毫秒。如果在此时间内被join线程还没有执行完毕,则不再等待。
3)join(long millis, int nanos):等待被join线程的时间最长为millis毫秒加nanos毫微秒(注:很少使用第3种形式的方法,因为程序对时间精度无须精确到毫微秒;计算机硬件本身也无法精确到毫微秒)。
如下代码:
class JoinThread extends Thread
{
public JoinThread(String name){
super(name);
}
public void run(){
for(int i = 0; i < 23; i++){
System.out.println(Thread.currentThread().getName() + " 输出 " + i);
}
}
}
public class JoinTest
{
public static void main(String[] args) throws InterruptedException
{
System.out.println(Thread.currentThread().getName() + "开始");
JoinThread jt1 = new JoinThread("New Thread");
JoinThread jt2 = new JoinThread("Thread joined");
jt1.start();
jt2.start();
jt2.join();//当调用join之后,main函数必须在jt2线程完成之后都会执行。
for(int i = 0; i < 23; i++){
System.out.println(Thread.currentThread().getName() + " 输出 " + i);
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
2. 线程睡眠---sleep
Thread类的静态方法sleep()可以让当前正在执行的线程暂停一段时间并进入阻塞状态(状态转换图参见多线程-1),其有两种重载形式:
public static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。
public static void sleep(long millis, int nanos):让当前正在执行的线程暂停millis毫秒加nanos毫微秒并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响,与join一样,第二种方法也不常用。
public class SleepTest
{
public static void main(String[] args) throws InterruptedException
{
System.out.println(Thread.currentThread().getName() + "开始");
for(int i = 0; i < 3; i++){
System.out.println(Thread.currentThread().getName() + " 输出 " + i);
System.out.println(Thread.currentThread().getName() + " 将睡眠... ");
Thread.sleep(2000);//转入阻塞状态
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
3.线程让步---yield
yield()方法与sleep()方法有点相似,也是Thread提供的静态方法,可以让当前正在执行的线程暂停,但不会阻塞线程,而是将该线程状态直接转入就绪状态(从前面线程状态图也可以看出)。yield()只是让当前线程暂停一下,让系统的线程调度器重新调度一次,当然也就有可能发生下列情况:当某个线程调用了yield()方法后,转为就绪状态,然后又马上被线程调度器调度重新执行。
实际上,当某个线程调用了yield()方法暂停之后,只有优先级与当前线程相同或优先级比当前线程更高的处于就绪状态的线程才会获得执行机会。
class YieldThread extends Thread
{
public YieldThread(String name){
super(name);
}
public void run(){
for(int i = 0; i < 23; i++){
if( i == 10){
System.out.println(getName() + " yielded cpu");
Thread.yield();
}
System.out.println(getName() + " 输出 " + i);
}
}
}
public class YieldTest
{
public static void main(String[] args)
{
System.out.println(Thread.currentThread().getName() + "开始");
YieldThread yt1 = new YieldThread("高级");
yt1.setPriority(Thread.MAX_PRIORITY);
yt1.start();
YieldThread yt2 = new YieldThread("低级");
yt2.setPriority(Thread.MIN_PRIORITY);
yt2.start();
System.out.println(Thread.currentThread().getName() + "结束");
}
}
sleep()与yield()方法相同与不同:
1)sleep()方法暂停当前线程与yield()方法使当前线程放弃执行权,都会给其他线程执行机会并且线程调度器在调度时会考虑线程优先级。
2)sleep()方法会将线程转入阻塞状态,直到睡眠时间完成才会转入就绪状态;而yield()不会将线程转入阻塞状态,而是强制当前线程进入就绪状态,完全有可能某个线程 调用yield()方法暂停后立即又执行。
3)sleep()方法声明抛出InterruptedException异常,所以调用sleep()方法时要么捕获异常要么抛出异常;而yield()没有声明抛出任何异常。
4)sleep()方法比yield()方法有更好的移植性,不建议使用yield()方法控制并发线程的执行。
4.后台线程---setDaemon
1)有一种线程是在后台运行的,其任务就是为其他线程提供服务,这种线程就被称为“后台线程”,又称“守护线程”或“精灵线程”。JVM的垃圾回收线程就是后台线程。
2)后台线程的特征就是:如果所有前台线程死亡,后台线程会自动死亡,因为所有前台线程死亡,即被服务对象全部死亡,后台服务线程没有存在的必要了;前台线程死亡之后,JVM会通知后台线程死亡,但从接收到指令到做出响应,需要时间。
3)调用Thread或其子类对象的setDaemon(true)方法可将指定线程设置成后台线程,将某个线程设置为后台线程必须要在此线程启动之前设置(即start()前台),否则引发IllegalThreadStateException。
4)Thread中的isDaemon()方法可以判断一个线程是不是后台线程。
5)有些线程默认是前台线程有些线程默认是后台线程,前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认为后台线程。
class DaemonThread extends Thread
{
public DaemonThread(String name){
super(name);
}
public void run(){
for(int i = 0; i < 2000; i++){
System.out.println(getName() + " 输出 "+ i);
}
}
}
public class DaemonTest
{
public static void main(String[] args)
{
DaemonThread dt = new DaemonThread("后台线程");
dt.setDaemon(true);
dt.start();
for(int i = 0; i < 3; i++){
System.out.println("主线程完成后,后台线程会结束");
}
}
}