博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JAVA 线程状态
阅读量:5264 次
发布时间:2019-06-14

本文共 9648 字,大约阅读时间需要 32 分钟。

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
waiters = 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

线程结束运行后即为此状态

状态转换

state-machine-example-java-6-thread-states.png

盗用一张图感觉是比较全的,不过自己认为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阻塞的过程划分到后者感觉也是不妥。

转载于:https://www.cnblogs.com/lailailai/p/4608255.html

你可能感兴趣的文章
数据结构(二):栈
查看>>
实训第五天
查看>>
平台维护流程
查看>>
SQL (FMDB)
查看>>
2012暑期川西旅游之总结
查看>>
Linux发行版的排行
查看>>
宾得镜头大全与发展史
查看>>
spread+wackamole打造全新高可用+负载均衡
查看>>
sql语句中的left join,right join,inner join的区别
查看>>
Xcode 快捷键及代码格式化
查看>>
在 Swift 项目中实现侧滑菜单-利用 SWRevealViewController
查看>>
Android JNI 传递对象
查看>>
Android TextView drawableLeft 在代码中实现
查看>>
函数定义从零开始学C++之从C到C++(一):const与#define、结构体对齐、函数重载name mangling、new/delete 等...
查看>>
字段方法“轻松”实现一次查询多表
查看>>
生成编辑UBIFS 创建记录
查看>>
程序启动冲出UAC-解决Win UAC问题的编程经验
查看>>
nullnullhow to read directory name using std c in the linux
查看>>
测试SQLServer拆分字符串到临时表
查看>>
安装版本Visual Studio打包(Windows Installer),你不知道的RemovePreviousVersions 属性
查看>>