线程的生命周期以及控制线程
一、线程的生命周期
线程状态转换图:
1、新建状态
用newkeyword和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。
注意:不能对已经启动的线程再次调用start()方法,否则会出现java.lang.IllegalThreadStateException异常。
2、就绪状态
处于就绪状态的线程已经具备了执行条件,但还没有分配到CPU。处于线程就绪队列(虽然是採用队列形式。其实,把它称为可执行池而不是可执行队列。由于cpu的调度不一定是依照先进先出的顺序来调度的),等待系统为其分配CPU。等待状态并非执行状态。当系统选定一个等待执行的Thread对象后。它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。
一旦获得CPU。线程就进入执行状态并自己主动调用自己的run方法。
提示:假设希望子线程调用start()方法后马上运行。能够使用Thread.sleep()方式使主线程睡眠一伙儿,转去运行子线程。3、执行状态
处于执行状态的线程最为复杂。它能够变为堵塞状态、就绪状态和死亡状态。
处于就绪状态的线程。假设获得了cpu的调度,就会从就绪状态变为执行状态,执行run()方法中的任务。假设该线程失去了cpu资源,就会又从执行状态变为就绪状态。又一次等待系统分配资源。也能够对在执行状态的线程调用yield()方法,它就会让出cpu资源。再次变为就绪状态。
当发生例如以下情况是。线程会从执行状态变为堵塞状态:
①、线程调用sleep方法主动放弃所占用的系统资源
②、线程调用一个堵塞式IO方法,在该方法返回之前,该线程被堵塞
③、线程试图获得一个同步监视器,但更改同步监视器正被其它线程所持有
④、线程在等待某个通知(notify)
⑤、程序调用了线程的suspend方法将线程挂起。只是该方法easy导致死锁。所以程序应该尽量避免使用该方法。
当线程的run()方法执行完。或者被强制性地终止,比如出现异常,或者调用了stop()、desyory()方法等等。就会从执行状态转变为死亡状态。
4、堵塞状态
处于执行状态的线程在某些情况下,如执行了sleep(睡眠)方法。或等待I/O设备等资源,将让出CPU并临时停止自己的执行,进入堵塞状态。
在堵塞状态的线程不能进入就绪队列。
仅仅有当引起堵塞的原因消除时,如睡眠时间已到,或等待的I/O设备空暇下来。线程便转入就绪状态。又一次到就绪队列中排队等待。被系统选中后从原来停止的位置開始继续执行。有三种方法能够暂停Threads执行:
5、死亡状态
当线程的run()方法运行完。或者被强制性地终止。就觉得它死去。这个线程对象或许是活的,可是。它已经不是一个单独运行的线程。
线程一旦死亡。就不能复生。 假设在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
二、线程状态的控制
Java提供了一些便捷的方法用于会线程状态的控制。 .
void |
destroy() 已过时。 该方法最初用于破坏该线程。但不作不论什么清除。它所保持的不论什么监视器都会保持锁定状态。 只是。该方法决不会被实现。 即使要实现,它也极有可能以 假设还有一个线程曾试图锁定该资源,则会出现死锁。这类死锁一般会证明它们自己是“冻结”的进程。 有关很多其它信息,请參阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?。 |
void |
interrupt() 中断线程。 |
void |
join() 等待该线程终止。 |
void |
join(long millis) 等待该线程终止的时间最长为 millis 毫秒。 |
void |
join(long millis, int nanos) 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。 |
void |
resume() 已过时。 该方法仅仅与 suspend() 一起使用。但 suspend() 已经遭到反对,由于它具有死锁倾向。有关很多其它信息。请參阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?。 |
void |
setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
void |
setPriority(int newPriority) 更改线程的优先级。 |
static void |
sleep(long millis) 在指定的毫秒数内让当前正在运行的线程休眠(暂停运行)。此操作受到系统计时器和调度程序精度和准确性的影响。 |
static void |
sleep(long millis, int nanos) 在指定的毫秒数加指定的纳秒数内让当前正在运行的线程休眠(暂停运行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
void |
start() 使该线程開始运行;Java 虚拟机调用该线程的 run 方法。 |
void |
stop() 已过时。 该方法具有固有的不安全性。 用 Thread.stop 来终止线程将释放它已经锁定的全部监视器(作为沿堆栈向上传播的未检查 假设目标线程等待非常长时间(比如基于一个条件变量),则应使用 |
void |
stop(Throwable obj) 已过时。 该方法具有固有的不安全性。有关具体信息,请參阅 stop() 。该方法的附加危急是它可用于生成目标线程未准备处理的异常(包含若没有该方法该线程不太可能抛出的已检查的异常)。有关很多其它信息,请參阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?。 |
void |
suspend() 已过时。 该方法已经遭到反对,由于它具有固有的死锁倾向。假设目标线程挂起时在保护关键系统资源的监视器上保持有锁,则在目标线程又一次開始曾经不论什么线程都不能訪问该资源。假设又一次開始目标线程的线程想在调用 resume 之前锁定该监视器。则会发生死锁。这类死锁一般会证明自己是“冻结”的进程。有关很多其它信息,请參阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?。 |
static void |
yield() 暂停当前正在运行的线程对象,并运行其它线程。 |
能够看到非常多方法。已经标注为过时的,我们应该尽可能的避免使用它们,而应该重点关注start()、interrupt()、join()、sleep()、yield()等直接控制方法,和setDaemon()、setPriority()等间接控制方法。
1、线程睡眠——sleep
假设我们须要让当前正在运行的线程暂停一段时间。并进入堵塞状态,则能够通过调用Thread的sleep方法。从上面能够看到sleep方法有两种重载的形式,可是用法一样。
比方,我们想要使主线程每休眠100毫秒。然后再打印出数字:
- public class Test1 {
- public static void main(String[] args) throws InterruptedException {
- for(int i=0;i<100;i++){
- System.out.println("main"+i);
- Thread.sleep(100);
- }
- }
- }
注意例如以下几点问题
①、sleep是静态方法。最好不要用Thread的实例对象调用它,由于它睡眠的始终是当前正在执行的线程,而不是调用它的线程对象。它仅仅对正在执行状态的线程对象有效。看以下的样例:
- public class Test1 {
- public static void main(String[] args) throws InterruptedException {
- System.out.println(Thread.currentThread().getName());
- MyThread myThread=new MyThread();
- myThread.start();
- myThread.sleep(1000);//这里sleep的就是main线程。而非myThread线程
- Thread.sleep(10);
- for(int i=0;i<100;i++){
- System.out.println("main"+i);
- }
- }
- }
②、Java线程调度是Java多线程的核心,仅仅有良好的调度,才干充分发挥系统的性能。提高程序的运行效率。
可是无论程序猿怎么编写调度。仅仅能最大限度的影响线程运行的次序,而不能做到精准控制。
由于使用sleep方法之后,线程是进入堵塞状态的,仅仅有当睡眠的时间结束。才会又一次进入到就绪状态。而就绪状态进入到运行状态。是由系统控制的,我们不可能精准的去干涉它。所以假设调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。
- public class Test1 {
- public static void main(String[] args) throws InterruptedException {
- new MyThread().start();
- new MyThread().start();
- }
- }
- class MyThread extends Thread {
- @Override
- public void run() {
- for (int i = 0; i < 3; i++) {
- System.out.println(this.getName()+"线程" + i + "次运行。");
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- Thread-0线程0次运行!
- Thread-1线程0次运行!
-
Thread-1线程1次运行!
-
Thread-0线程1次运行!
- Thread-0线程2次运行。
-
Thread-1线程2次运行!
能够看到它并非依照sleep的顺序运行的。
2、线程让步——yield
yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也能够让当前正在运行的线程暂停,让出cpu资源给其它的线程。可是和sleep()方法不同的是,它不会进入到堵塞状态,而是进入到就绪状态。yield()方法仅仅是让当前线程暂停一下,又一次进入就绪的线程池中。让系统的线程调度器又一次调度器又一次调度一次,全然可能出现这种情况:当某个线程调用yield()方法之后。线程调度器又将其调度出来又一次进入到运行状态运行。
实际上。当某个线程调用了yield()方法暂停之后,优先级与当前线程同样,或者优先级比当前线程更高的就绪状态的线程更有可能获得运行的机会。当然,仅仅是有可能,由于我们不可能精确的干涉cpu调度线程。
yield的使用方法:
- public class Test1 {
- public static void main(String[] args) throws InterruptedException {
- new MyThread("低级", 1).start();
- new MyThread("中级", 5).start();
- new MyThread("高级", 10).start();
- }
- }
- class MyThread extends Thread {
- public MyThread(String name, int pro) {
- super(name);// 设置线程的名称
- this.setPriority(pro);// 设置优先级
- }
- @Override
- public void run() {
- for (int i = 0; i < 30; i++) {
- System.out.println(this.getName() + "线程第" + i + "次运行。");
- if (i % 5 == 0)
- Thread.yield();
- }
- }
- }
①、sleep方法暂停当前线程后,会进入堵塞状态,仅仅有当睡眠时间到了,才会转入就绪状态。
而yield方法调用后 ,是直接进入就绪状态。所以有可能刚进入就绪状态,又被调度到执行状态。
②、sleep方法声明抛出了InterruptedException。所以调用sleep方法的时候要捕获该异常。或者显示声明抛出该异常。而yield方法则没有声明抛出任务异常。
③、sleep方法比yield方法有更好的可移植性,通常不要依靠yield方法来控制并发线程的运行。
3、线程合并——join
从上面的方法的列表能够看到,它有3个重载的方法:
void join()
当前线程等该增加该线程后面,等待该线程终止。
void join(long millis)
当前线程等待该线程终止的时间最长为 millis 毫秒。
假设在millis时间内,该线程没有运行完,那么当前线程进入就绪状态,又一次等待cpu调度
void join(long millis,int nanos)
等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。假设在millis时间内。该线程没有运行完,那么当前线程进入就绪状态。又一次等待cpu调度
- public class Test1 {
- public static void main(String[] args) throws InterruptedException {
- MyThread thread=new MyThread();
- thread.start();
- thread.join(1);//将主线程增加到子线程后面,只是假设子线程在1毫秒时间内没运行完。则主线程便不再等待它运行完,进入就绪状态,等待cpu调度
- for(int i=0;i<30;i++){
-
System.out.println(Thread.currentThread().getName() + "线程第" + i + "次运行!
");
- }
- }
- }
- class MyThread extends Thread {
- @Override
- public void run() {
- for (int i = 0; i < 1000; i++) {
-
System.out.println(this.getName() + "线程第" + i + "次运行!
");
- }
- }
- }
在这个样例中,在主线程中调用thread.join(); 就是将主线程增加到thread子线程后面等待运行。只是有时间限制,为1毫秒。
4、线程的优先级
每一个线程运行时都有一个优先级的属性,优先级高的线程能够获得较多的运行机会。而优先级低的线程则获得较少的运行机会。与线程休眠类似。线程的优先级仍然无法保障线程的运行次序。仅仅只是,优先级高的线程获取CPU资源的概率较大,优先级低的也并不是没机会运行。
每一个线程默认的优先级都与创建它的父线程具有同样的优先级,在默认情况下。main线程具有普通优先级。
Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,当中setPriority方法的參数是一个整数,范围是1~·0之间。也能够使用Thread类提供的三个静态常量:
MAX_PRIORITY =10
MIN_PRIORITY =1
NORM_PRIORITY =5
样例:
- public class Test1 {
- public static void main(String[] args) throws InterruptedException {
- new MyThread("高级", 10).start();
- new MyThread("低级", 1).start();
- }
- }
- class MyThread extends Thread {
- public MyThread(String name,int pro) {
- super(name);//设置线程的名称
- setPriority(pro);//设置线程的优先级
- }
- @Override
- public void run() {
- for (int i = 0; i < 100; i++) {
- System.out.println(this.getName() + "线程第" + i + "次运行!");
- }
- }
- }
注意一点:尽管Java提供了10个优先级别。但这些优先级别须要操作系统的支持。
不同的操作系统的优先级并不同样,并且也不能非常好的和Java的10个优先级别相应。所以我们应该使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设定优先级,这样才干保证程序最好的可移植性。
5、守护线程
public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在执行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。 该方法首先调用该线程的 checkAccess 方法,且不带不论什么參数。这可能抛出 SecurityException(在当前线程中)。
參数:
on - 假设为 true,则将该线程标记为守护线程。
抛出:
IllegalThreadStateException - 假设该线程处于活动状态。
SecurityException - 假设当前线程无法改动该线程。
- /**
- * Java线程:线程的调度-守护线程
- */
- public class Test {
- public static void main(String[] args) {
- Thread t1 = new MyCommon();
- Thread t2 = new Thread(new MyDaemon());
- t2.setDaemon(true); //设置为守护线程
- t2.start();
- t1.start();
- }
- }
- class MyCommon extends Thread {
- public void run() {
- for (int i = 0; i < 5; i++) {
- System.out.println("线程1第" + i + "次运行。");
- try {
- Thread.sleep(7);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- class MyDaemon implements Runnable {
- public void run() {
- for (long i = 0; i < 9999999L; i++) {
- System.out.println("后台线程第" + i + "次运行!");
- try {
- Thread.sleep(7);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
运行结果:
- 后台线程第0次运行!
-
线程1第0次运行!
-
线程1第1次运行!
- 后台线程第1次运行!
- 后台线程第2次运行。
- 线程1第2次运行。
- 线程1第3次运行。
-
后台线程第3次运行!
-
线程1第4次运行!
-
后台线程第4次运行!
- 后台线程第5次运行。
- 后台线程第6次运行!
-
后台线程第7次运行!
守护线程的用途:
守护线程通经常使用于执行一些后台作业,比如在你的应用程序执行时播放背景音乐。在文字编辑器里做自己主动语法检查、自己主动保存等功能。
Java的垃圾回收也是一个守护线程。
守护线
的优点就是你不须要关心它的结束问题。比如你在你的应用程序执行的时候希望播放背景音乐。假设将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候。
不仅要退出主线程,还要通知播放背景音乐的线程退出;假设设定为守护线程则不须要了。
6、怎样结束一个线程
Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit这些终止线程执行的方法已经被废弃了。使用它们是极端不安全的!想要安全有效的结束一个线程,能够使用以下的方法。
1、正常运行完run方法,然后结束掉
2、控制循环条件和推断条件的标识符来结束掉线程
比方说run方法这样写:
- class MyThread extends Thread {
- int i=0;
- @Override
- public void run() {
- while (true) {
- if(i==10)
- break;
- i++;
- System.out.println(i);
- }
- }
- }
或者
- class MyThread extends Thread {
- int i=0;
- boolean next=true;
- @Override
- public void run() {
- while (next) {
- if(i==10)
- next=false;
- i++;
- System.out.println(i);
- }
- }
- }
或者
- class MyThread extends Thread {
- int i=0;
- @Override
- public void run() {
- while (true) {
- if(i==10)
- return;
- i++;
- System.out.println(i);
- }
- }
- }
仅仅要保证在一定的情况下,run方法可以运行完成就可以。而不是while(true)的无线循环。
3、使用interrupt结束一个线程。
诚然,使用第2中方法的标识符来结束一个线程,是一个不错的方法。可是假设,该线程是处于sleep、wait、join的状态的时候,while循环就不会运行,那么我们的标识符就无用武之地了,当然也不能再通过它来结束处于这3种状态的线程了。
能够使用interrupt这个巧妙的方式结束掉这个线程。
我们看看sleep、wait、join方法的声明:
- public final void wait() throws InterruptedException
- public static native void sleep(long millis) throws InterruptedException
- public final void join() throws InterruptedException
在什么时候会产生这样一个异常呢?
每一个Thread都有一个中断状状态,默觉得false。能够通过Thread对象的isInterrupted()方法来推断该线程的中断状态。能够通过Thread对象的interrupt()方法将中断状态设置为true。
当一个线程处于sleep、wait、join这三种状态之中的一个的时候,假设此时他的中断状态为true,那么它就会抛出一个InterruptedException的异常,并将中断状态又一次设置为false。
看以下的简单的样例:
- public class Test1 {
- public static void main(String[] args) throws InterruptedException {
- MyThread thread=new MyThread();
- thread.start();
- }
- }
- class MyThread extends Thread {
- int i=1;
- @Override
- public void run() {
- while (true) {
- System.out.println(i);
- System.out.println(this.isInterrupted());
- try {
- System.out.println("我立即去sleep了");
- Thread.sleep(2000);
- this.interrupt();
- } catch (InterruptedException e) {
- System.out.println("异常捕获了"+this.isInterrupted());
- return;
- }
- i++;
- }
- }
- }
- 1
- false
- 我立即去sleep了
- 2
- true
- 我立即去sleep了
- 异常捕获了false
所以,我们能够使用interrupt方法结束一个线程。
详细使用例如以下:
- public class Test1 {
- public static void main(String[] args) throws InterruptedException {
- MyThread thread=new MyThread();
- thread.start();
- Thread.sleep(3000);
- thread.interrupt();
- }
- }
- class MyThread extends Thread {
- int i=0;
- @Override
- public void run() {
- while (true) {
- System.out.println(i);
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- System.out.println("中断异常被捕获了");
- return;
- }
- i++;
- }
- }
- }
- 0
- 1
- 2
- 中断异常被捕获了
- 0
- 1
- 2
- 3
- 中断异常被捕获了
这两种结果恰恰说明了 仅仅要一个线程的中断状态一旦为true,仅仅要它进入sleep等状态。或者处于sleep状态,立刻回抛出InterruptedException异常。
第一种情况,是当主线程从3秒睡眠状态醒来之后。调用了子线程的interrupt方法。此时子线程正处于sleep状态,立刻抛出InterruptedException异常。
第一种情况。是当主线程从3秒睡眠状态醒来之后。调用了子线程的interrupt方法,此时子线程还没有处于sleep状态。然后再第3次while循环的时候,在此进入sleep状态。立刻抛出InterruptedException异常。