1、线程的生命周期
线程的生命周期就是一个线程从创建到消亡的过程,如图
新建状态(new)
用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。处于新建状态的线程有自己的内存空间,通过调用start方法进入就绪状态。
就绪状态(Runnable)
处于就绪状态的线程已经具备了运行条件(也就是具备了在CPU上运行的资格),但还没有分配到CPU的执行权,处于“线程就绪队列”,等待系统为其分配CPU。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
运行状态(Running)
处于就绪状态的线程,如果获得了CPU的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。如果该线程失去了CPU资源,就会又从运行状态变为就绪状态,重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出CPU资源,再次变为就绪状态。
阻塞状态(Blocked)
在某种特殊的情况下,被人挂起或执行输入输出操作时,让出CPU执行权并临时中断自己的执行,从而进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。
阻塞产生的原因不同,阻塞状态又分为三种:
1、等待阻塞:
运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态。当调用notify()或notifyAll()等方法,则该线程就会重新转入就绪状态。
2、同步阻塞:
线程在获取同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。当获取同步锁成功,则该线程就会重新转入就绪状态。
3、其他阻塞:
通过调用线程sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,则该线程就会重新转入就绪状态。
死亡状态(Dead)
线程在run()方法执行完了或者因异常退出了run()方法,该线程结束生命周期。此外,如果线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。
2、线程状态控制
1、线程睡眠(sleep)
如果我们需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,指定时间之后,解除阻塞状态,进入就绪状态,则可以通过调用Thread的sleep方法。
注意:
1、sleep是静态方法,最好不要用Thread的实例对象调用它,因为它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象,它只对正在运行状态的线程对象有效。
2、使用sleep后,线程进入阻塞状态,睡眠时间过后才会重新进入就绪状态,但从就绪状态到运行状态是由系统决定的,我们不能精准控制,所以调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。
public static void main(String[] args) {
for(int i = 1; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(2000); // 对主线程休眠2秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2、线程的优先级
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。
Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,能够设置[1-10]之间的整数,数值越大,那么优先级越高,也可以使用Thread类提供的三个静态常量:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
class TestThread extends Thread {
public TestThread() {}
public TestThread(String name, int pro) {
super(name); // 设置线程名字
this.setPriority(pro); // 设置线程的优先级
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + "线程--第" + i + "次执行!");
}
}
}
public class TestDemo{
public static void main(String[] args) {
new TestThread("高级",10).start();
new TestThread("低级",1).start();
}
}
运行代码,可以看到一般情况下设置比重较高的会先执行,比重低的后执行
3、线程让步
yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也可以让当前正在执行的线程暂停,让出CPU资源给其它的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。
class TestThread extends Thread {
public TestThread() {}
public TestThread(String name, int pro) {
super(name); // 设置线程名字
this.setPriority(pro); // 设置线程的优先级
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(this.getName() + "线程--第" + i + "次执行!");
//线程让步
Thread.yield();
}
}
}
public class TestDemo{
public static void main(String[] args) {
new TestThread("高级",10).start();
new TestThread("低级",1).start();
}
}
运行结果,可以明显看到比重低也会出现在前面执行
4、线程合并
线程的合并就是:线程A在运行期间,可以调用线程B的join()方法,这样线程A就必须等待线程B执行完毕后,才能继续执行。
应用场景:当一个线程必须等待另一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能。
class TestThread extends Thread {
public TestThread() {}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName() + "线程--第" + i + "次执行!");
}
}
}
public class TestDemo{
public static void main(String[] args) {
TestThread th = new TestThread();
th.start();
try{
th.join();
}catch (InterruptedException e){
e.printStackTrace();
}
//主线程任务
for (int i = 0; i < 10; i++) {
String name = Thread.currentThread().getName();
System.out.println(name + "线程第" + i + "次执行!");
}
}
}
运行结果,主线程会等待其他线程执行完才会执行
5、守护线程
守护线程通常用于执行一些后台作业,守护线的好处就是你不需要关心它的结束问题。当普通线程(前台线程)都全部执行完毕,也就是当前在运行的线程都是守护线程时,Java虚拟机(JVM)将退出。
守护线程与普通线程写法上基本没啥区别,调用线程对象的方法setDaemon(true),就可以把该线程标记为守护线程。setDaemon(true)方法必须在启动线程前调用,否则抛出IllegalThreadStateException异常。
//前台线程
class CommonThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("前台线程" + i + "次执行!");
}
}
}
//后台线程
class DaemonThread extends Thread {
int i = 0;
@Override
public void run() {
while(true) {
System.out.println("后台线程第" + i++ + "次执行!");
}
}
}
public class Test{
public static void main(String[] args) {
CommonThread ct = new CommonThread();
DaemonThread dt = new DaemonThread();
ct.start();
dt.setDaemon(true); // 将dt设置为守护线程
dt.start();
}
}
运行结果,可以看到,前台线程是保证执行完毕了,只剩下后台线程(守护线程)时,jvm会将其退出。