Java线程状态


线程的状态转换是线程控制的基础,下面我们看一下图

1. 新建(new):新创建了一个线程对象。
2. 可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。 该状态的线程位于可运行线程池中, 等待被线程调度选中, 获取cpu的使用权。
3. 运行(running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
4. 阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:

. 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。

. 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

. 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

5. 死亡(dead):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

第一、线程优先级
线程总是存在优先级,JVM线程调度程序是基于优先级的抢先调度机制。优先级为1~10之间的正整数,JVM从不会改变一个线程的优先级,然而,1~10之间的值是没有保证的。一些JVM可能不能识别10个不同的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级,但这仅仅是大多数情况。注意:当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作。当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:一是选择一个线程运行, 直到它阻塞或者运行完成为止。 二是时间分片,为池内的每个线程提供均等的运行机会。线程默认的优先级是创建它的执行线程的优先级。可以通过setPriority(int newPriority)更改线程的优先级。例如:

Thread t = new MyThread();
t.setPriority(8);
t.start();

线程默认优先级是5,Thread类中有三个常量,定义线程优先级范围:

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

第二、什么是守护线程
在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。 Daemon的作用是为其他线程的运行提供服务, 比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离, 那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:
public final void setDaemon(boolean on) ;
但是有几点需要注意:
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。
你不能把正在运行的常规线程设置为守护线程。
在Daemon线程中产生的新线程也是Daemon的。
不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。
(备注:注意与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养)

第三、线程离开运行状态的3种方法:
1、调用Thread.sleep():使当前线程睡眠至少多少毫秒(尽管它可能在指定的时间之前被中断)。
2、调用Thread.yield():不能保障太多事情,尽管通常它会让当前运行线程回到可运行性状态,使得有相同优先级的线程有机会执行。
3、调用join()方法:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。

第四、如何停止线程
停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。停止一个线程可以使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。
在Java中有以下3种方法可以终止正在运行的线程:
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。

第五、yield()为什么我们没有用过?
要理解yield(),必须了解线程的优先级的概念。使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的, 因为让步的线程还有可能被线程调度程序再次选中。调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

第六、我们为什么使用join()方法
在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。


至于wait 和notify方法,我们再专门讨论。

 

posted on 2016-08-02 11:55  mh-lhw  阅读(238)  评论(0编辑  收藏  举报

导航