Java 线程状态
定义都在Thread.State里,通过线程对象的getState()
可以获取得到。
NEW
新创建的还没有启动的线程所处的状态。
RUNNABLE
在JVM中正在执行的线程的状态。也不一定是正在执行代码,也可能是在处于一个等待调度以获取CPU的状态,不过一般这个调度时间非常短暂,对外界来说线程一直是在连续执行的。这个和操作系统里进程调度相关情况一致。yield
可以让出线程当前占用的CPU但是线程还是处于RUNNABLE状态。
BLOCKED
等待获取内部锁时所处的状态。这里的等待和RUNNABLE状态中可能发生的等待就不一样了,它可能会非常的长,在产生死锁的代码上,线程会一直在等待。如下会产生死锁的代码
import java.util.concurrent.*;/** * Created by hegaofeng on 6/29/15. */public class DeadLockThreads { public static void main(String args[]) throws InterruptedException, ExecutionException { final Trouble trouble = new Trouble(); trouble.addOne(); System.out.println(trouble.getCount()); }}class Trouble { private ExecutorService pool = Executors.newCachedThreadPool(); private int cnt = 0; public synchronized void addOne() throws InterruptedException, ExecutionException { Future future = pool.submit(new Callable() { @Override public Object call() throws Exception { System.out.println("try to preparing"); prepare(); System.out.println("prepared."); cnt++; return "done"; } }); System.out.println(future.get()); } private synchronized void prepare() { System.out.println("preparing"); } public synchronized int getCount() { return cnt; }}
注意synchronized
对应的内部锁本身是可重入的。也就是说一个线程在一个synchronized
修饰的方法里调用其他本类内也被synchronized
修饰的方法并不会引起死锁,比如递归。但是两个及多个不同的线程在一个对象上执行被synchronized
修饰的方法就要格外小心,因为它们可能会产生相互依赖。在synchronized
等待时线程就处于BLOCKED
状态。
除了类似上面比较明显的使用方式会使得线程处于BLOCKED
状态外,还有一种情况比较隐蔽,不过也是处于获取内部锁的时候。下面是一段典型的使用内置锁类进行条件等待的例子:
synchronized(SomeObject) { while (ConditionCheckNotMeet) { SomeObject.wait() } // modify state protected by SomeObject internal lock SomeObject.notifyAll();}
一个具体的例子:
import java.util.concurrent.*;/** * Created by hegaofeng on 6/29/15. */public class ConditionWait { public static void main(String[] args) throws InterruptedException { ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor(); final SimpleBlockingQueue queue = new SimpleBlockingQueue(4); Runnable poper = new Runnable() { @Override public void run() { try { int pop = queue.pop(); System.out.println("poped : " + pop); } catch (InterruptedException e) { e.printStackTrace(); } } }; pool.scheduleAtFixedRate(poper, 1000, 500, TimeUnit.MILLISECONDS); for (int i=0; i<10000; i++) { queue.push(i); System.out.println("pushed: " + i); } }}class SimpleBlockingQueue { private int size = 0; private int cur = 0; private int[] storage; public SimpleBlockingQueue(int cap) { cap = cap > 0 ? cap : 0; storage = new int[cap]; } public void push(int val) throws InterruptedException { synchronized (storage) { while (size >= storage.length) { // 1. we hold the lock of storage storage.wait(); // 2. we lost the lock of storage when entering wait state // 3. when somebody invoke notify or notifyAll: // wait return when we retrieved the lock again } storage[cur] = val; cur = (cur + 1) % storage.length; size++; storage.notifyAll(); } } public int pop() throws InterruptedException { int val; synchronized (storage) { while (size <= 0) { storage.wait(); } int idx = ((cur - size) + storage.length) % storage.length; val = storage[idx]; size--; storage.notifyAll(); } return val; }}
这里实现了一个有界的阻塞队列,在push
和pop
函数中都使用到了storage.wait()
这表示放弃在该对象上的内部锁,并在该对象上等待(进入WAITING
状态)。
wait
方法来自Object
类,所以所有的java对象都可以这样来使用。但在调用wait
方法前我们必须获得该对象上的锁。这个原因,需要联系synchronized
-wait
-notify
的使用配合方式。从此调用这个方法的线程只会当其他线程在该对象上调用notify
或者notifyAll
时可能醒来(除开其他异常情况)。wait
的返回并不是在其他线程调用notify
后立刻进行的。它先会进行几个阶段的工作
- 重新竞争去获取对象的内部锁,此时状态从
WAITING
变为为BLOCKED
- 如果获得对象内部锁,切换线程状态到
RUNNABLE
,wait
返回 - 如果没有获取到内部锁则,切换线程状态重新回到
WAITING
,继续等待。
以上是两种处于BLOCKED
的线程状态的情况,都是和内置锁相关的。
WAITING
表示线程处于等待的状态,这种等待是没有时间限制的,即可一直等下去,不会超时。能够进入这个状态的调用有:
- Object.wait
- Thread.join
- LockSupport.park
Object.wait
这是Object
的实例方法,在调用该方法前必须要获取对象的内置锁,否则会抛出java.lang.IllegalMonitorStateException
异常。因为wait
方法本身会去释放已经获得的对象内置锁(因为自己要进行睡眠了,拿着锁毫无用处,其他线程没有锁也不敢改由改对象内置锁保护的数据,于是自己也永远醒不了了),然而我们并没有获得锁。基于这个使用场景的要求,wait
必然是在synchronized
代码块或者方法中使用的。
Thread.join
观察JDK代码可以发现这是个synchronized
修饰的方法,也是一个条件等待,内部也是调用了Object.wait
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
这个是有超时参数的版本,其中参数millis
为0时即为无限等待的版本。
LockSupport.park
这个见的比较少,但是它会被许多的基础的并发同步类用到,如CountDownLatch
,Semaphore
,ReentrantLock
等,也就说并发包里几乎所有同步工具类都使用了LockSupport
类。这个类也不是直接被使用而是通过一个叫做AbstractQueuedSynchronizer
的抽象类。
LockSupport.park
可以使得线程进入WAITING
状态,它不像Object.wait
需要获得对象锁,它只是使线程进入睡眠状态WAITING
,不进行额外的操作(如放入某个等待队列,这是使用LockSupport那些类需要做的)。park只能由要进入WAITING
状态的线程自己执行。另外可以通过LockSupport.unpark
唤醒指定进程。
注意unpack
不能唤醒不是通过LockSupport
进入睡眠的线程(如不能唤醒调用sleep而睡眠的线程),下面是一个例子:
import java.io.BufferedInputStream;import java.io.IOException;import java.util.concurrent.locks.LockSupport;/** * Created by hegaofeng on 6/29/15. */public class LockSupportTry { public static void main(String[] args) throws IOException { Thread other = new Thread(new Runnable() { @Override public void run() { while (true) { System.out.println("go sleeping"); LockSupport.park(); for (int i=0; i<10; i++) { System.out.println("."); } } } }); other.setDaemon(true); other.start(); BufferedInputStream is = new BufferedInputStream(System.in); int n = 0; while ((n = is.read()) > 0) { System.out.println("last state:" + other.getState()); LockSupport.unpark(other); } }}每按一次回车可以唤醒other线程。
另外直接贴一个LockSupport的javadoc中的例子:
class FIFOMutex { private final AtomicBoolean locked = new AtomicBoolean(false); private final Queuewaiters = new ConcurrentLinkedQueue (); public void lock() { boolean wasInterrupted = false; Thread current = Thread.currentThread(); waiters.add(current); // Block while not first in queue or cannot acquire lock while (waiters.peek() != current || !locked.compareAndSet(false, true)) { LockSupport.park(this); if (Thread.interrupted()) // ignore interrupts while waiting wasInterrupted = true; } waiters.remove(); if (wasInterrupted) // reassert interrupt status on exit current.interrupt(); } public void unlock() { locked.set(false); LockSupport.unpark(waiters.peek()); } }
TIMED_WAITING
此状态是有时间限制的睡眠即超过该时间后线程醒来。能够使得线程进入这个状态的和WAITING
类似:
- Object.wait
- Thread.join
- LockSupport.parkUntil
- sleep
只不过相比WAITING
中的那些多了一个超时时间值。使用方法一致。
TERMINATED
线程结束运行后即为此状态
状态转换
盗用一张图感觉是比较全的,不过自己认为WAITING到RUNNABLE也是可以通过LockSupport.unpark
做到的。 JStack 查看当前线程状态
先用jps
命令查看当前运行的JVM,然后通过jstack jvm_pid
来打印该JVM中所有线程的状态和栈。此时我们会发现WAITING
一个状态中有些小的备注信息如:
- WAITING (parking)
- WAITING (on object monitor)
两者代表了两类进入WAITING状态的途径即Object.wait
和LockSupport.park
。由于JDK并发包种的工具类大量使用了LockSupport,在一些阻塞的地方基本上都是第一类的情况。
一些问题
进行网络I/0时线程状态
下面是一个简单的echo服务(端口为2015),可以发现不管是accept阻塞时还是read阻塞时线程状态总是为RUNNABLE
,凭感觉来讲应该是处于WAITING
之类的才是。
import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.ServerSocket;import java.net.Socket;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * Created by hegaofeng on 6/29/15. */public class NetworkBlocking { public static void main(String[] args) throws IOException { ExecutorService pool = Executors.newCachedThreadPool(); ServerSocket serverSocket = new ServerSocket(2015); while (true) { System.out.println("Server listening " + serverSocket.getInetAddress()); Socket client = serverSocket.accept(); pool.submit(new ServiceRunnable(client)); } }}class ServiceRunnable implements Runnable { private final Socket clientSocket; public ServiceRunnable(Socket socket) { clientSocket = socket; } @Override public void run() { System.out.println("new echo service for client:" + clientSocket.getInetAddress()); try { InputStream is = clientSocket.getInputStream(); OutputStream os = clientSocket.getOutputStream(); int n = 0; int data; while ((n = is.read()) >= 0) { os.write(n); } } catch (IOException e) { e.printStackTrace(); } }}
如果像一些资料所述的那样把RUNNABLE
状态分为两种:
- RUNNING 确实在占用CPU,跑代码
- READY 没有占用CPU,等待调度
但把这个网络IO阻塞的过程划分到后者感觉也是不妥。