JAVA 线程状态
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 Queue<Thread> waiters
= new ConcurrentLinkedQueue<Thread>();
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阻塞的过程划分到后者感觉也是不妥。