zoukankan      html  css  js  c++  java
  • 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阻塞的过程划分到后者感觉也是不妥。

  • 相关阅读:
    分层图最短路(DP思想) BZOJ2662 [BeiJing wc2012]冻结
    动态规划 BZOJ1925 地精部落
    线性DP SPOJ Mobile Service
    线性DP codevs2185 最长公共上升子序列
    数位DP POJ3208 Apocalypse Someday
    线性DP POJ3666 Making the Grade
    杨氏矩阵 线性DP? POJ2279 Mr.Young's Picture Permutations
    tarjan强连通分量 洛谷P1262 间谍网络
    树链剖分 BZOJ3589 动态树
    二分图 BZOJ4554 [Tjoi2016&Heoi2016]游戏
  • 原文地址:https://www.cnblogs.com/lailailai/p/4608255.html
Copyright © 2011-2022 走看看