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;
    }
}

这里实现了一个有界的阻塞队列,在pushpop函数中都使用到了storage.wait()这表示放弃在该对象上的内部锁,并在该对象上等待(进入WAITING状态)。

wait方法来自Object类,所以所有的java对象都可以这样来使用。但在调用wait方法前我们必须获得该对象上的锁。这个原因,需要联系synchronized-wait-notify的使用配合方式。从此调用这个方法的线程只会当其他线程在该对象上调用notify或者notifyAll时可能醒来(除开其他异常情况)。wait的返回并不是在其他线程调用notify后立刻进行的。它先会进行几个阶段的工作

  1. 重新竞争去获取对象的内部锁,此时状态从WAITING变为为BLOCKED
  2. 如果获得对象内部锁,切换线程状态到RUNNABLEwait返回
  3. 如果没有获取到内部锁则,切换线程状态重新回到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

这个见的比较少,但是它会被许多的基础的并发同步类用到,如CountDownLatchSemaphoreReentrantLock等,也就说并发包里几乎所有同步工具类都使用了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一个状态中有些小的备注信息如:

  1. WAITING (parking)
  2. WAITING (on object monitor)

两者代表了两类进入WAITING状态的途径即Object.waitLockSupport.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状态分为两种:

  1. RUNNING 确实在占用CPU,跑代码
  2. READY 没有占用CPU,等待调度

但把这个网络IO阻塞的过程划分到后者感觉也是不妥。

posted @ 2015-06-29 18:26  卖程序的小歪  阅读(588)  评论(0编辑  收藏  举报