java线程状态及sleep、wait、notify、notifyAll
2016年08月10日

一、Java中线程的状态

Thread.State枚举类,定义了5种状态:

public enum State {
   /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

即:

  • NEW 新建状态,线程创建且没有执行start方法时的状态,也称之为初始状态、开始状态

  • RUNNABLE 可运行状态,线程已经启动,但是等待相应的资源(比如IO或者时间片切换)才能开始执行

  • BLOCKED 阻塞状态,当遇到synchronized或者lock且没有取得相应的锁,就会进入这个状态

  • WAITING 等待状态,当调用Object.wait或者Thread.join()且没有设置时间,在或者LockSupport.park时,都会进入等待状态。

  • TIMED_WAITING 计时、限时等待,当调用Thread.sleep()或者Object.wait(xx)或者Thread.join(xx)或者LockSupport.parkNanos或者LockSupport.partUntil时,进入该状态

  • TERMINATED 终止状态,线程中断或者运行结束的状态


注意,JVM中线程为什么没有 running状态?因为Java线程,和操作系统的线程,实际上是两回事,状态也不完全一致,Java线程的runnable状态,实际上包含了操作系统线程的running和ready to run两种可能,JVM没法区分——因为底层操作系统的线程,running与否,是由操作系统决定的,JVM无法控制,而且更重要的是,区分running与ready to run没有多少意义,因为这两者时刻都在变化,如下描述:


现在的时分(time-sharing)多任务(multi-task)操作系统架构通常都是用所谓的“时间分片(time quantum or time slice)”方式进行抢占式(preemptive)轮转调度(round-robin式)。


这个时间分片通常是很小的,一个线程一次最多只能在 cpu 上运行比如10-20ms 的时间(此时处于 running 状态),也即大概只有0.01秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度(也即回到 ready 状态)


现今主流的 JVM 实现都把 Java 线程一一映射到操作系统底层的线程上,把调度委托给了操作系统,我们在虚拟机层面看到的状态实质是对底层状态的映射及包装。JVM 本身没有做什么实质的调度,把底层的 ready 及 running 状态映射上来也没多大意义,因此,统一成为runnable 状态是不错的选择。


下面是Thread.State源码中的注释中的话:

These states are virtual machine states which do not reflect any operating system thread states。

这些状态是虚拟机状态,它不反映任何操作系统的线程状态。

RUNABLE状态的注释:

A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.

处于 runnable 状态下的线程正在 Java 虚拟机中执行,但它可能正在等待来自于操作系统的其它资源,比如处理器。


Java中的 RUNNABLE 状态 实际上是包含了 Ready与Running的状态的,所以你完全可以无视网上那些不准确的说法,这种问题的答案往往就在源码与javadoc中。


另外,所谓的线程死亡(Dead)状态,实际上是口语,和书面语 TERMINATED 终止状态,是一个意思,都是代表线程已经执行结束,无论是正常的结束还是异常退出。 


另外,注意到,调用JAVA API得到的线程状态,不一定是操作系统线程的真实状态,那只是一个Java程序中的一个标记而已,但是JVM有一些方法来保证程序的有序性,比如happens-before原则中有如下三条:

  • 线程启动规则(Thread Start Rule):Thread独享的start()方法先行于此线程的每一个动作。 

  • 线程终止规则(Thread Termination Rule):线程中的每个操作都先行发生于对此线程的终止检测。例如通过Thread.join()方法等待线程结束,Thread.isAlive()的返回值检测线程已经终止执行,当join或者isAlive通过时,那线程就确实已经终止了。

  • 线程中断规则(Thread Interruption Rule):对线程interrupte()方法的调用优先于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测线程是否已中断。


如果没有上面的原则,由于JAVA程序本身无法判断 操作系统底层的线程 是否已经开始、终止和中断,那么代码执行顺序 和 实际执行顺序 就会不一致,所以针对这个问题,JVM底层要做特殊处理。


参见:

https://www.cnblogs.com/trust-freedom/p/6606594.html


https://www.cnblogs.com/jijijiefang/articles/7222955.html


http://www.cnblogs.com/paddix/p/5381958.html



二、Java线程和操作系统线程的关系

https://blog.csdn.net/cringkong/article/details/79994511

https://www.cnblogs.com/wangzhongqiu/archive/2018/04/21/8903060.html


简单的说:

    现在的Java中线程的本质(JDK1.2及以后),其实就是操作系统中的线程,Linux下是基于pthread库实现的轻量级进程(LWP),Windows下是原生的系统Win32 API提供系统调用从而实现多线程。


而对不同的操作系统,由于本身设计思路不一样,对于线程的设计也存在种种差异,所以JVM在设计上,就已经声明:

    虚拟机中的线程状态,不反应任何操作系统线程状态。

所以,Java线程和操作系统线程,实际上同根同源,但又相差甚远。


从实际意义上来讲,操作系统中的线程除去new和terminated状态,一个线程真实存在的状态,只有

  • ready:表示线程已经被创建,正在等待系统调度分配CPU使用权。

  • running:表示线程获得了CPU使用权,正在进行运算

  • waiting:表示线程等待(或者说挂起),让出CPU资源给其他线程使用

为什么除去new和terminated状态?是因为这两种状态实际上并不存在于线程运行中,所以也没什么实际讨论的意义。


对于Java中的线程状态:

  • 无论是Timed Waiting Waiting还是Blocked,对应的都是操作系统线程的waiting(等待)状态。 

  • Runnable状态,则对应了操作系统中的readyrunning状态。


三、Java线程状态转换的方法、Java对象监视器


涉及到状态转换的方法有:

  • Thread.sleep(long):强制线程睡眠一段时间。

  • thread.start():启动一个线程。

  • thread.join():在当前线程中加入指定线程,使得这个指定线程等待当前线程,并在当前线程结束前结束。

  • thread.yield():使得当前线程退让出CPU资源,把CPU调度机会分配给同样线程优先级的线程。

  • thread.interrupt():使得指定线程中断阻塞状态,并将阻塞标志位置为true。

  • object.wait()、object.notify()、object.notifyAll():Object类提供的线程等待和线程唤醒方法。


wait(long timeout)

Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsedwait()方法与wait(0)等效


notify()

Wakes up a single thread that is waiting on this object's monitor.

notify和notifyAll的区别在于前者只能唤醒该对象monitor上的一个线程(并且我们不知道哪个线程会收到通知),而notifyAll则唤醒该对象monitor上的所有线程。换言之,如果只有一个线程在等待一个信号灯,notify和notifyAll都会通知到这个线程。但如果多个线程在等待这个信号灯,那么notify只会通知到其中一个,而其它线程并不会收到任何通知。


需要注意的是,在线程调用Object实例的notify方法后,并不会立刻释放Object实例对象锁,而是synchronized同步块的代码执行完了(跳出synchronized同步块)之后,线程才会释放Object实例对象锁。


为何wait、notify、notifyAll这三个不是Thread类声明中的方法,而是Object类中声明的方法?(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)

其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。


sleep和wait的区别

  1. sleep是Thread类的方法,而wait是Object类的方法。

  2. sleep方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep方法的过程中,线程不会释放对象锁。而当调用wait方法的时候,线程会放弃对象锁,进入等待此对象的等待池,只有针对此对象调用notify方法后本线程才进入对象锁池准备。


wait 和 notify 使用原则

  1. 对在多线程间共享的那个Object来使用wait,并在wait之前给被共享的对象上锁(synchronized)

  2. 永远在循环(loop)里调用 wait 和 notify,不是在 If 语句;

  3. 基于前文提及的理由,更倾向用 notifyAll(),而不是 notify()。

  4. 当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O或控制台I/O),一定不要持有锁。


使用wait和notify函数的规范代码模板: 注意while循环

synchronized (sharedObject) { 
    while (condition) { 
        sharedObject.wait(); 
        // (Releases lock, and reacquires on wakeup) 
    }
    // do action based upon condition e.g. take or put into queue 
}

以 生产者—消费者 为例,

public class ProducerConsumerInJava { 

    class Producer extends Thread 
    { private Queue<Integer> queue; 
        private int maxSize; 
        public Producer(Queue<Integer> queue, int maxSize, String name) { 
            super(name); this.queue = queue; this.maxSize = maxSize; 
        } 
        @Override public void run() { 
            while (true) { 
                    synchronized (queue) { 
                        while (queue.size() == maxSize) {
                            try { 
                                System.out .println("Queue is full"); 
                                queue.wait(); 
                            } catch (Exception ex) {
                                ex.printStackTrace(); } 
                            }
                            Random random = new Random(); 
                            int i = random.nextInt(); 
                            System.out.println("Producing value : " + i); 
                            queue.add(i);
                            queue.notifyAll();
                        } 
                    } 
                } 
            } 

    class Consumer extends Thread { 
        private Queue<Integer> queue; 
        private int maxSize; 
        public Consumer(Queue<Integer> queue, int maxSize, String name){ 
            super(name); 
            this.queue = queue; 
            this.maxSize = maxSize; 
        } 
        @Override public void run() { 
            while (true) { 
                synchronized (queue) { 
                    while (queue.isEmpty()) { 
                        System.out.println("Queue is empty"); 
                        try { 
                            queue.wait(); 
                        } catch (Exception ex) { 
                            ex.printStackTrace(); 
                        }
                    } 
                    System.out.println("Consuming value : "
                    + queue.remove()); 
                    queue.notifyAll(); 
                } 
            } 
        } 
    }

    public static void main(String args[]) { 
        System.out.println("How to use wait and notify method in Java"); 
        System.out.println("Solving Producer Consumper Problem"); 
        Queue<Integer> buffer = new LinkedList<>(); 
        int maxSize = 10; 
        Thread producer = new Producer(buffer, maxSize, "PRODUCER"); 
        Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); 
        producer.start(); consumer.start(); 
    }
}

注意,在java 1.5中提供了Condition类,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效,JDK中的阻塞队列实际上是使用了Condition来模拟线程间协作。


调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在try-finally的lock.lock()和lock.unlock之间才可以使用

  Conditon中的await()对应Object的wait();

  Condition中的signal()对应Object的notify();

  Condition中的signalAll()对应Object的notifyAll()。

上例改为 condition 来实现,关键代码如下:

// 使用重入锁
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();

private void consume() {
    while(true){
        lock.lock();
        try {
            while(queue.isEmpty()){
                System.out.println("队列空,等待数据");
                try {
                    notEmpty.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            queue.poll();  //每次移走队首元素
            notFull.signal();
            System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
        } finally{
            lock.unlock();
        }
    }
}

private void produce() {
    while(true){
        lock.lock();
        try {
            while(queue.size() == queueSize){
                System.out.println("队列满,等待有空余空间");
                try {
                    notFull.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            queue.offer(1);//每次插入一个元素
            notEmpty.signal();
            System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
        } finally{
            lock.unlock();
        }
    }
}


yield()

yield方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将Running状态转变为Runnable状态


关于yield方法,java docs是这样描述的:

    A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.

    调度器可能会忽略该方法。

    Yield is a heuristic attempt to improve relative progression between threads that would otherwise over-utilise a CPU. Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the desired effect.

    使用的时候要结合实际的测试和观测,确保能达到预期的效果。

    It is rarely appropriate to use this method. It may be useful for debugging or testing purposes, where it may help to reproduce bugs due to race conditions. It may also be useful when designing concurrency control constructs such as the ones in the package.

    很少有场景要用到该方法,主要使用的地方是调试,以及并发控制例如JDK中的java.util.concurrent.locks.AbstractQueuedSynchronizer(实际上也只用到了一处,如下:

final boolean transferAfterCancelledWait(Node node) {
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node);
        return true;
    }
    /*
     * If we lost out to a signal(), then we can't proceed
     * until it finishes its enq().  Cancelling during an
     * incomplete transfer is both rare and transient, so just
     * spin(自旋).
     */
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

该方法被 await(long time, TimeUnit unit) 调用。其作用是,在await期间,希望降低自己对CPU的占用频率,while过程中能把CPU让出来,让其他线程先获取CPU。


join(long millis)

Waits at most millis milliseconds for this thread to die. 其实现与wait方法类似,join()方法实际上执行的join(0)。

从源码可以看出join方法就是通过wait方法来将线程的阻塞如果join的线程还在执行,则将当前线程阻塞起来,直到join的线程执行完成,当前线程才能执行。不过有一点需要注意,这里的join只调用了wait方法,却没有对应的notify方法,原因是Thread的start方法中做了相应的处理,所以当join的线程执行完成以后,会自动唤醒主线程继续往下执行



Java对象监视器(ObjectMonitor)

在JVM的规范中,有这么一些话:

  • “在JVM中,每个对象和类在逻辑上都是和一个监视器相关联的”

  • “为了实现监视器的排他性监视能力,JVM为每一个对象和类都关联一个锁”

  • “锁住了一个对象,就是获得对象相关联的监视器”


监视器好比一做建筑,它有一个很特别的房间,房间里有一些数据,而且在同一时间只能被一个线程占据,

  • 进入这个建筑叫做"进入监视器"(monitorenter)

  • 进入建筑中的那个特别的房间叫做"获得监视器"

  • 占据房间叫做"持有监视器"

  • 离开房间叫做"释放监视器"

  • 离开建筑叫做"退出监视器"(monitorexit)

  • (monitorenter和monitorexit是JVM的指令)

而一个锁就像一种任何时候只允许一个线程拥有的特权。

一个线程可以允许多次对同一对象上锁。对于每一个对象来说,java虚拟机维护一个计数器,记录对象被加了多少次锁。没被锁的对象的计数器是0,线程每加锁一次,计数器就加1,每释放一次,计数器就减1。当计数器跳到0的时候,锁就被完全释放了。


注意:通常所谓的“对象锁”、“对象monitor”,表达的都是一个意思(虽然它们其实并不是同一个东西)。


在oracle JVM 1.6 里面实现的object的wait 和notify方法是在synchronizer.cpp里实现。 里面有两个核心对象:ObjectMonitor,ObjectWaiter


ObjectWaiter对象里存放thread(线程对象) 和 ParkEvent(线程的unpark);

ObjectMonitor  对象 主要用来监视创立的Object;ObjectMonitor对象里有2个队列成员_WaitSet 和 _EntryList 存放的就是ObjectWaiter

_WaitSet:

主要存放所有wait的线程的对象,也就是说如果有线程处于wait状态,将被挂入这个队列

_EntryList:

所有在等待获取锁的线程的对象,也就是说如果有线程处于等待获取锁的状态的时候,将被挂入这个队列。

具体参见:

https://www.cnblogs.com/549294286/p/3688829.html

https://www.jianshu.com/p/f4454164c017

https://cloud.tencent.com/developer/article/1013062