线程的生命周期:
当线程被创建并启动后,它并不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(New),就绪(Runnable),运行(Running),阻塞(Blocked)和死亡(Dead) 5种状态。
新建(New)
使用new关键字创建一个线程之后,该线程就处于新建状态,此时它和其他Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化了其成员变量。此时的线程对象没有表现出任何现成的动态特征,程序也不会执行线程执行体中的线程执行体。
就绪(Runnable)
当线程对象调用了start()方法后,此时该线程处于就绪状态。Java虚拟机为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,它只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。
注意:
#1 启动线程使用start方法,而不是run方法!调用start方法来启动线程,系统会把该run方法当成线程执行体来处理。但如果直接调用线程对象的run方法,则run方法立即就会被执行,且在run方法返回之前其他线程无法并发执行----也就是说系统吧线程对象当成普通对象,而run方法仅是一个普通方法,而不是线程执行体。
#2 不要对已经处于启动状态的线程再次调用start方法,否则将引发IllegalThreadStateException异常。
3. 运行(Running)
如果处于就绪状态的线程获得了CPU,开始执行run方法的线程执行体,则该线程处于运行状态。如果计算机只有一个CPU,在任何时刻只有一条线程处于运行状态。当然,在一个多处理器的机器上,将会有多个线程并行执行。但当线程数大于处理器数时,依然会有多条线程在同一个CPU上轮换的现象。
4. 阻塞(Blocked)
当发生如下情况,线程将进入阻塞状态:
a. 线程调用sleep方法主动放弃所占用的处理器资源;
b. 线程调用了一个阻塞式 IO方法,在该方法返回之前,该线程被阻塞;
c. 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有;
d. 线程在等待某个通知(notify);
e. 程序调用了线程的suspend方法将线程挂起。
解除阻塞的线程后,该线程重新进入就绪状态,等待线程调度器再次调度它。
5. 线程死亡(Dead)
线程会以以下三种方式之一结束,结束后该线程处于死亡状态:
a. run()方法执行完成,线程正常结束;
b. 线程抛出一个未捕获的Exception或Error;
c. 直接调用该线程的stop()方法来结束该线程---易导致死锁,不使用!
注意: 当主线程结束时,其他线程不受任何影响,并不会随之结束,一旦子线程启动后,它就拥有和主线程相同的地位,它不会受主线程的影响。
为测试某条线程是否已经死亡,可以调用线程对象的isAlive()方法,当线程处于就绪、运行、阻塞三种状态时,该方法将返回true;当线程处于新建、死亡两种状态时,该方法将返回false。
控制线程:
Java的线程支持提供了一些简便的方法,通过这些方法可以很好地控制线程的执行.
join线程
Thread提供了一个线程等待另一个线程完成的方法----join()方法. 当在某个执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法计入的join线程执行完为止.
package org.santorini.thread;
public class JoinTest {
public static void main(String[] args) throws Exception {
new JoinThread("新线程").start();;
for (int i=0; i<100; i++) {
if (i == 20) {
JoinThread jt = new JoinThread("被Join的线程");
jt.start();
//main线程调用了jt线程的join()方法,main线程必须等待jt线程执行结束后才会向下执行
jt.join();
}
System.out.println(Thread.currentThread().getName() + "^^^^^" + i);
}
}
}
class JoinThread extends Thread {
public JoinThread(String name) {
super(name);
}
@Override
public void run() {
for (int i=0; i<50; i++) {
System.out.println(getName() + "---" + i);
}
}
}
说明: 程序中共有3个线程,main方法中开始就启动了名为"新线程"的子线程,该线程启动后与主线程拥有同等地位,并发执行。当主线程的循环变量 i=20 时,启动了名为"被Join的线程",该线程不会和主线程并发执行,而是主线程必须等该线程执行结束后才可以向下执行,这段时间内主线程处于等待状态。
join方法有如下3中重载形式:
-
join(): 等待被join的线程执行完成;
join(long millis): 等待被join的线程的时间最长为millis毫秒。如果在millis毫秒内被join的线程还没有之行结束,则不再等待;
join(long millis, int nanos): 等待被join的线程的时间最长为millis毫秒加nanos微毫秒。
2. 后台线程
有一种线程,它是在后台运行的,它的任务是为其他的线程提供服务,这种线程被称为"后台线程"。又称为"守护线程"或"精灵线程"(PS:好吧,我喜欢叫它"精灵线程",略显调皮~)。JVM的垃圾回收线程就是典型的后台线程。
后台线程的特征: 如果所有的前台线程都死亡,后台线程会自动死亡.
调用Thread对象的setDaemon(true)方法可将指定线程设置为精灵线程,
Thread类还提供了一个isDaemon()方法,用于判断指定线程是否为后台线程。
注意:
a. 前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程;
b. 前台线程死亡后,JVM会通知后台线程死亡,但从它接受指令到做出响应,需要一定时间;
c. 将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说,setDaemon(true)必须start()方法之前调用,否则会引发IllegalThreadStateException异常.
3. 线程睡眠:sleep
如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread类的静态sleep()方法来实现。
当当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行.
4. 线程让步: yield
yield()方法是一个和sleep方法有点类似的方法,它也是一个Thread类提供的静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次。完全可能的情况是: 当某个线程调用了yield方法暂停之后,线程调度又将其调度出来重新执行.
关于sleep方法和yield方法的区别如下:
A. sleep方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级。但yield方法只会给优先级相同,或优先级更高的线程执行机会;
B. sleep方法将方法转入阻塞状态,直到经过阻塞事件才会转入就绪状态。而yield不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。
C. sleep方法声明抛出了InterruptedException异常,所以调用sleep方法时要么捕捉该异常,要么显式声明抛出该异常。而yield方法则没有声明抛出异常。
改变线程优先级:
每个线程执行时都有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会。
每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级,由main线程创建的子线程也有普通优先级。
Thread提供了setPriority(int newPriority)和getPriority()方法来设置和返回执行线程的优先级,其中setPriority方法的参数可以使一个整数,范围是1~10之间,也可以是Thread类的三个静态常量:
MAX_PRIORITY: 其值是10;
MIN_PRIORITY: 其值是1;
NORM_PRIORITY: 其值是5;