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阻塞的过程划分到后者感觉也是不妥。

  • 相关阅读:
    OCP-1Z0-052-V8.02-44题
    Oracle DB 使用调度程序自动执行任务
    OCP-1Z0-053-V12.02-497题
    OCP-1Z0-053-V12.02-513题
    OCP-1Z0-053-V12.02-650题
    OCP-1Z0-053-V12.02-680题
    OCP-1Z0-052-V8.02-42题
    OCP-1Z0-052-V8.02-130题
    OCP-1Z0-052-V8.02-132题
    Mysql中的from_unixtime,unix_timestamp 整型与datetime 字符串之间的转换
  • 原文地址:https://www.cnblogs.com/lailailai/p/4608255.html
Copyright © 2011-2022 走看看