3、线程的生命周期:
线程的生命周期包括线程的新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。
当需要新起一个线程来执行某个子任务时,就 新建(New)了一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪(Runnable)状态。
当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行(Running)状态。
当线程在运行状态过程中,线程调用了一个阻塞式IO方法、线程试图获得一个同步监视器、线程等待某个通知、调用sleep方法放弃所占用的处理器资源等,将会进入阻塞(Blocked)状态。
当由于异常或者子任务执行完毕,线程就会死亡(Dead)。
线程状态转换图:
4、Java中线程常用的方法:
4.1、start方法:
启动线程使用start方法,而不是run方法,永远不要使用线程对象的run方法。调用start方法来启动线程,系统会把该run方法当成执行体来处理。如果调用使用run方法,那么run方法将会立即执行,而且在run方法返回之前其他线程无法并发执行–也就是说系统将会将Thread对象作为一个普通的对象来处理,而此时Run方法只是一个普通的方法,而不是线程执行体。
4.2、run方法:
线程的执行体,线程创建之前应该重写执行体方法来完成该线程的业务逻辑。
4.3、sleep方法:
当我们需要当前正在执行的线程暂停一段时间,并且进入阻塞状态,可以通过调用Thread类的静态sleep方法。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。
sleep方法提供两种重载方法:
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos)throws InterruptedException;
4.4、yield方法:
yield方法和sleep方法相似,都是让当前进程暂停一下,但是不能控制具体的交出CPU的时间。使当前进程进入就绪状态,可以通过调用Thread类的静态yield方法。
yield方法只是让当前进程暂停一下,让系统的线程调度重新调度一次。实际上,当某个线程调用yield方法暂停后,只有优先级大于等于当前线程的就绪状态线程才可能获得执行机会。
//Thread类的静态yield方法
public static native void yield();
//设置和获取线程对象的优先级
public final void setPriority();
public final int getPriority()
4.5、join方法:
Thread提供一个线程等待另一个线程完成的方法:join()方法。线程中调用其他线程的join方法,调用线程将会进入阻塞状态,直至其他线程完成为止。
实际上调用join方法是调用了Object的wait方法,这个可以通过查看源码得知。wait方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。由于wait方法会让线程释放对象锁,所以join方法同样会让线程释放对一个对象持有的锁。
提供三种重载方式:
join()
join(long millis) //参数为毫秒
join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
已经废弃的方法:stop、suspend、resume、destroy,这些方法在实现上时不安全的。
5、守护线程:
Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。
这里有几点需要注意:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。