1、线程的生命周期html
线程状态转换图:java
其中Thread.join()调用的是Object.wait()方法实现的,意思是让当前线程等待。是当前调用thread1.join()的线程等待,而不是让thread1这个线程等待。程序员
一、新建状态数据库
用new关键字和Thread类或其子类创建一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有本身的内存空间,经过调用start方法进入就绪状态(runnable)。安全
注意:不能对已经启动的线程再次调用start()方法,不然会出现java.lang.IllegalThreadStateException异常。多线程
2、就绪状态并发
处于就绪状态的线程已经具有了运行条件,但尚未分配到CPU,处于线 程就绪队列(尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列。由于cpu的调度不必定是按照先进先出的顺序来调度的),等待系统为其分配 CPU。等待状态并非执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动做称之为“cpu调 度”。一旦得到CPU,线程就进入运行状态并自动调用本身的run方法。异步
提示:若是但愿子线程调用start()方法后当即执行,可使用Thread.sleep()方式使主线程睡眠一伙儿,转去执行子线程。编辑器
三、运行状态ide
处于运行状态的线程最为复杂,它能够变为阻塞状态、就绪状态和死亡状态。
处于就绪状态的线程,若是得到了cpu的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。若是该线程失去了cpu资源,就会又从运 行状态变为就绪状态。从新等待系统分配资源。也能够对在运行状态的线程调用yield()方法,它就会让出cpu资源,再次变为就绪状态。
当发生以下状况是,线程会从运行状态变为阻塞状态:
①、线程调用sleep方法主动放弃所占用的系统资源
②、线程调用一个阻塞式IO方法,在该方法返回以前,该线程被阻塞
③、线程试图得到一个同步监视器,但更改同步监视器正被其余线程所持有
④、线程在等待某个通知(notify)
⑤、程序调用了线程的suspend方法将线程挂起。不过该方法容易致使死锁,因此程序应该尽可能避免使用该方法。
当线程的run()方法执行完,或者被强制性地终止,例如出现异常,或者调用了stop()、desyory()方法等等,就会从运行状态转变为死亡状态。
四、阻塞状态
处于运行状态的线程在某些状况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时中止本身的运行,进入阻塞状态。阻塞的状况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁(synchronized)被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其余阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程从新转入就绪状态。
在阻塞状态的线程不能进入就绪队列。只有当引发阻塞的缘由消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,从新到就绪队列中排队等待,被系统选中后从原来中止的位置开始继续运行。有三种方法能够暂停Threads执行;
五、死亡状态
当线程的run()方法执行完,或者因异常退出而被强制性地终止,就认为它死去。这个线程对象也许是活的,可是,它已经不是一个单独执行的线程。线程一旦死亡, 就不能复生。 若是在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
锁池:Java多线程中有两种同步锁synchronized和Lock,其中Lock关键字是JDK1.5以后新加入的锁,锁具备排他性,当一个线程得到锁以后,其余线程只能等待其余线程释放该锁,等待的线程也就进入了锁池。
等待池:当线程调用Object.wait()或者Condition.await()时,程序所在的线程会释放其所占有的资源(相应的会释放 synchronized和Lock锁),而进入等待池,等待池当中的线程会等待其余线程调用 Object.notifyAll(),Object.notify()或 者 Condition.signalAll(),Condition.signal()唤醒,这样进入等待的线程就进入等待池,从等待池出来以后进入锁池,得到锁以后即可进行工做了。
须要说明的是,synchronized锁和调用wait()的对象应为同一对象!不然会报java.lang.IllegalMonitorStateException错误。正确方式以下:
public synchronized static void function04() { //类锁 try { Test05.class.wait();//本类的wait池 } catch (InterruptedException e) { e.printStackTrace(); } } public void function02() { synchronized (lock) {//lock锁 try { lock.wait();//一样为lock锁的wait池 } catch (InterruptedException e) { e.printStackTrace(); } } }
2、线程状态的控制
Java提供了一些便捷的方法用于会线程状态的控制。 .
void |
destroy() 已过期。 该方法最初用于破坏该线程,但不做任何清除。它所保持的任何监视器都会保持锁定状态。不过,该方法决不会被实现。即便要实现,它也极有可能以 suspend() 方式被死锁。若是目标线程被破坏时保持一个保护关键系统资源的锁,则任何线程在任什么时候候都没法再次访问该资源。若是另外一个线程曾试图锁定该资源,则会出现死锁。这类死锁一般会证实它们本身是“冻结”的进程。有关更多信息,请参阅为什么不同意使用 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 来终止线程将释放它已经锁定的全部监视器(做为沿堆栈向上传播的未检查 ThreadDeath 异常的一个天然后果)。若是之前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其余线程可见,这有可能致使任意的行为。stop 的许多使用都应由只修改某些变量以指示目标线程应该中止运行的代码来取代。目标线程应按期检查该变量,而且若是该变量指示它要中止运行,则从其运行方法依次返回。若是目标线程等待很长时间(例如基于一个条件变量),则应使用interrupt 方法来中断该等待。有关更多信息,请参阅为什么不同意使用 Thread.stop、Thread.suspend 和 Thread.resume?。 |
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()等间接控制方法。
一、线程睡眠——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); } } }
运行结果:
main
Thread-0线程0次执行!
Thread-0线程1次执行!
Thread-0线程2次执行!
main0
main1
......
②、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次执行!
能够看到,线程0首先执行,而后线程1执行一次,又了执行一次。能够看到它并非按照sleep的顺序执行的。
二、线程让步——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(); } } }
运行结果:
高级线程第0次执行!
高级线程第1次执行!
高级线程第2次执行!
高级线程第3次执行!
高级线程第4次执行!
中级线程第0次执行!
低级线程第0次执行!
低级线程第1次执行!
低级线程第2次执行!
低级线程第3次执行!
低级线程第4次执行!
中级线程第1次执行!
中级线程第2次执行!
中级线程第3次执行!
中级线程第4次执行!
......
关于sleep()方法和yield()方的区别以下:
①、sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态,因此有可能刚进入就绪状态,又被调度到运行状态。
②、sleep方法声明抛出了InterruptedException,因此调用sleep方法的时候要捕获该异常,或者显示声明抛出该异常。而yield方法则没有声明抛出任务异常。
③、sleep方法比yield方法有更好的可移植性,一般不要依靠yield方法来控制并发线程的执行。
三、线程合并——join
线程的合并的含义就是将几个并行的线程合并为一个单线程执行,应用场景是当一个线程必须等待另外一个线程执行完毕才能执行时,Thread类提供了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-0线程第0次执行!
Thread-0线程第1次执行!
Thread-0线程第2次执行!
Thread-0线程第3次执行!
Thread-0线程第4次执行!
Thread-0线程第5次执行!
Thread-0线程第6次执行!
Thread-0线程第7次执行!
Thread-0线程第8次执行!
Thread-0线程第9次执行!
main线程第0次执行!
Thread-0线程第10次执行!
main线程第1次执行!
......
在这个例子中,在主线程中调用thread.join(); 就是将主线程加入到thread子线程后面等待执行。不过有时间限制,为1毫秒。
四、线程的优先级
每一个线程执行时都有一个优先级的属性,优先级高的线程能够得到较多的执行机会,而优先级低的线程则得到较少的执行机会。与线程休眠相似,线程的优先级仍然没法保障线程的执行次序。只不过,优先级高的线程获取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三个静态常量来设定优先级,这样 才能保证程序最好的可移植性。
五、守护线程
守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则能够将其设置为守护线程。
守护线程使用的状况较少,但并不是无用,举例来讲,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在作数据库应用时候,使用的数据库链接池,链接池自己也包含着不少后台线程,监控链接个数、超时时间、状态等等。
setDaemon方法的详细说明:
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次执行!
从上面的执行结果能够看出:前台线程是保证执行完毕的,后台线程尚未执行完毕就退出了。
实际上:JRE判断程序是否执行结束的标准是全部的前台执线程行完毕了,而无论后台线程的状态,所以,在使用后台县城时候必定要注意这个问题。
守护线程的用途:
守护线程一般用于执行一些后台做业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里作自动语法检查、自动保存等功能。Java的垃圾回收也是一个守护线程。守护线
的好处就是你不须要关心它的结束问题。例如你在你的应用程序运行的时候但愿播放背景音乐,若是将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候,
不只要退出主线程,还要通知播放背景音乐的线程退出;若是设定为守护线程则不须要了。
六、如何结束一个线程
Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit这些终止线程运行的方法已经被废弃了,使用它们是极端不安全的!想要安全有效的结束一个线程,可使用下面的方法。
一、正常执行完run方法,而后结束掉
二、控制循环条件和判断条件的标识符来结束掉线程
好比说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)的无线循环。
三、使用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
能够看到,这三者有一个共同点,都抛出了一个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
能够看到,首先执行第一次while循环,在第一次循环中,睡眠2秒,而后将中断状态设置为true。当进入到第二次循环的时候,中断状态就 是第一次设置的true,当它再次进入sleep的时候,立刻就抛出了InterruptedException异常,而后被咱们捕获了。而后中断状态又 被从新自动设置为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异常。
我我的比较推崇的作法是使用标识符来标识当前线程的运行状态:
class MyThread extends Thread { public volatile boolean isRun = true; @Override public void run() { //ToDo:进行一些必要的初始化操做 while (isRun) { //ToDo:执行你须要进行的异步操做 } //ToDo:释放线程占用的资源或者作必要的清除工做 } }
当知足必定条件以后通常是在另一个线程,好比主线程去设置当前线程能够结束了,将线程的运行标识符isRun设置为false.能够看到我对isRun使用了volatile关键字,让其在线程间可见.同时在进入到while循环以前咱们能够作一些线程的初始化操做,好比创建数据库链接,初始化变量什么的,在线程将要结束退出while循环以后进行一些必要的释放资源和清理工做.上面使用interrupt结束线程的方法我认为是很拙劣的,线程之因此会进入阻塞状态就是由于当前线程尚未进行完该线程该完成的任务,而你忽然去中断当前的线程会让你的程序处于不可预知的状态,同时无谓的sleep操做会形成无谓的频繁的线程切换.