Java线程的状态
上图来源于网络资源,本人加了一些文字注释有关状态的转换
针对上图进行说明:
- 线程通过start()启动后进入到可运行状态,在此状态下,一旦锁释放,由JVM调度程序自动调度。如果有多个可运行程序,并且优先级相同,由JVM进行选择;
- 线程运行的唯一前置状态是可运行,因此在等待或者睡眠状态的线程,在运行之前必须先为可运行状态。这就是如果一个线程在锁上调用了wait()方法,则将无休止的等待,如果没有其他线程把它唤醒,他将永远不会再运行。
public class PrintNum {
private byte[] lock = new byte[0]; // 自定义锁对象,这样代价最小,也可已使用当前对象this
public void demo() {
PrintThread a = new PrintThread("a");
PrintThread b = new PrintThread("b");
PrintThread c = new PrintThread("c");
PrintThread d = new PrintThread("d");
a.start();
b.start();
c.start();
d.start();
}class PrintThread extends Thread {
public PrintThread(String name) {
setName(name);
}public void run() {
synchronized (lock) {
for (int i = 0; i < 100; i++) {
if (i % 10 == 0 && 0 != i) {
try {
System.out.println(this.getName() + ": i="+i+",lock.wait()");
lock.wait(); // 暂时释放资源System.out.println(this.getName() + ": i="+i+",notify()");
lock.notify(); // 唤醒另外一个进程} catch (Throwable e) {
e.printStackTrace();
}
}
System.out.println(this.getName() + ": " + i);
}
}
}
}
public static void main(String[] args) {
PrintNum printNum = new PrintNum();
printNum.demo();
}
}如上代码分析:
- 依次启动了a,b,c,d 4个线程,线程进入到可运行状态。由于a最先被启动,所以它往往最先被执行;
- 当a运行到i=10的时候进入到阻塞状态,没有机会执行下面的lock.notify();
- 由于a把锁释放,因此JVM自动从可运行的线程中选择一个运行,可能是b,c,d,假设是b;
- 当b运行到i=10的时候进入到阻塞状态,没有机会执行下面的lock.notify();
- 由于b把锁释放,因此JVM自动从可运行的线程中选择一个运行,可能是c,d,假设是c;
- 当c运行到i=10的时候进入到阻塞状态,没有机会执行下面的lock.notify();
- 由于c把锁释放,因此JVM自动从可运行的线程中选择一个运行,这时只有b在可运行队列中,所以是d;
- 当d运行到i=10的时候进入到阻塞状态,没有机会执行下面的lock.notify();
- 此时可运行队列为空,没有线程需要JVM调度到运行状态;
- 等待队列中有4个线程,但它们都没有被唤醒,所以将持续等待,程序无法执行完毕而退出,进入死锁状态。
解决办法:
将notify()调用放在wait()调用之前,这样可以保证可运行队列中始终有线程可被调度为运行状态。
-
线程由‘新’到‘可运行’是通过 Thread.start()方法实现的;
-
线程由‘可运行’到‘运行’,是由 操作系统负责调度的,这部分对JVM来说是透明的;
- 线程由‘运行’到‘等待’,是通过lock.wait方法实现的,wait方法有多个重载方法,wait(0)/wait()代表一直等待,直到被notify;
- 线程由‘等待’到‘可运行’,是通过lock.notify/notifyAll方法实现的;
- 线程由’运行’到’睡眠’,是通过Thread.sleep(long)方法实现的,sleep方法的参数是指定了睡眠的时间。在睡眠过程中,对锁的占有并不释放,因此其他同步该锁的线程没有机会运行,但其它非同步该锁的线程是有机会运行的(任何优先级的线程都有机会,因为它不参与CPU的竞争)。当睡眠时间到期,则自动返回到可运行状态。尽管占有锁,但需要和其他非同步线程竞争CPU资源,是否能够运行取决于操作系统的调度;
- 线程由‘运行’到‘可运行’是通过Thread.yield()方法实现的,yield不会释放锁,因此其他同步的线程没有机会运行,但具有相同优先级的其他线程有机会运行。但是,实际中无法保证yield()能达到让步目的,因为让步的线程还有可能被线程调度程序再次选中(往往是这样)。
- 另外,Thread.join的作用是等待该线程终止,例如当前A线程执行到某步时需要B线程运行结果,因此需要其完成才能继续进行,这时在A线程内调用B.join方法等待B执行结束再执行。join方法有多个重载方法,可以指定等待的时间join(long);join(0)/join()代表一直等待。 本人感觉这个方法特别适合于等待可能超时的请求上,为了防止无休止的等待而占用系统的资源,可以对这样的请求用一个单独的线程,通过join方法指定等待的时间,超过时间后,跳过. 通过状态的检查来判断其是否正常结束,如果没有结束,则退出,返回超时的应答。