【20220608作业①】线程的BLOCKED状态和WAITING状态的区别

BLOCKED状态

一、是什么

BLOCKED是线程的一种状态,Thread.state中对其的介绍:

        /**
         * 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

翻译:

等待监视器锁的线程阻塞的线程状态。一个处于阻塞状态的线程正在等待一个监视器锁进入一个同步的块/方法或在调用Object.wait之后重新进入一个同步的块/方法

理解:表示线程当前正在等待获取到锁进入同步块/方法

二、什么情况下会使线程进入BLOCKED状态

解读上述翻译内容,线程有两种情况:①等待监视器锁进入同步块/方法;②调用object.wait后重新进入一个同步块/方法。

1. 等待监视器锁进入同步块/方法

1)监视器锁

监视器锁是什么?

任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。Synchronized在JVM里的实现都是
基于进入和退出Monitor对象来实现方法同步和代码块同步,虽然具体实现细节不一样,但是都可以通过成对的MonitorEnter和
MonitorExit指令来实现。(注1)
加锁过程图示(注1)

MonitorEnter进入(注2)

每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  1. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
  2. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
  3. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;
    思考:为什么允许重新进入?
MonitorExit退出(注2)

执行monitorexit的线程必须是对象引用所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。 其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

2)解读 - 等待监视器锁进入同步块/方法

等待监视器锁进入同步块/方法,也就是使用synchroinzed关键字标注某个类或者某个方法,标注的代码部分就是同步块/方法,** 等待进入该方法的线程的状态就是BLOCKED阻塞状态 **。

synchroinzed原理分析

synchroinzed本质是给当前类或指定某个对象加锁。结合监视器锁的概念总结,synchroinzed是在编译时** 对这个类添加ACC_SYNCHRONIZED标志 ** 或者 ** 在方法代码内容前后添加monitorenter 和 monitorexit 指令 **,利用Monitor监视器锁实现方法与代码块同步。(注3)

对象加锁的情况:

 private static Object object = new Object();
 
    public static void testSync(){
        synchronized(object) {
            System.out.println("当前占用线程: " + Thread.currentThread().getName());
            try {
                Thread.sleep(100000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

对象加锁字节码打印:
对象锁
思考:为什么会有两个monitorexit?

类加锁的情况:

 public static synchronized void testSync(){
        System.out.println("当前占用线程: " + Thread.currentThread().getName());
        try {
            Thread.sleep(100000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

类加锁字节码打印:
类锁

3)示例

以类锁情况为例:

package com.example.demo.thread;

/**
 * @author: dongzhengbei
 * @since: 2022.06.28 10:15
 */
public class BlockAndWait {

    public static synchronized void testSync(){
        System.out.println("当前占用线程: " + Thread.currentThread().getName());
        try {
            Thread.sleep(100000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread a = new Thread(()->{
            testSync();
        }, "Thread-A");

        Thread b = new Thread(()->{
            testSync();
        }, "Thread-B");
        a.start();
        b.start();
        System.out.println("Thread-A 状态:" + a.getState());
        System.out.println("Thread-B 状态:" + b.getState());
        try {
            Thread.sleep(100000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程状态打印:
日志打印
jstack线程信息:
jstack线程信息

2. 调用object.wait后重新进入一个同步块/方法

解读:①要调用object.wait;②调用之后要重新进入:notify之后继续执行wait之后的逻辑代码;
可知,BLOCKED状态是由于线程Object.wait被唤醒后未及时获取到锁,从而进入阻塞状态。

1)示例

public class BlockAndWait {

    private static Object object = new Object();

    public static void testSync(){
        try {
            synchronized(object) {
                System.out.println("test2-obj-当前占用线程: " + Thread.currentThread().getName());
                object.wait();
                System.out.println("test2-obj-唤醒后: " + Thread.currentThread().getName());
                Thread.sleep(1000000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void testSync3(){
        try{
            synchronized(object) {
                object.notify();
                System.out.println("test3-当前占用线程: " + Thread.currentThread().getName());
                Thread.sleep(1000000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread a = new Thread(()->{
            testSync();
        }, "Thread-A");

        Thread b = new Thread(()->{
            testSync3();
        }, "Thread-B");
        a.start();
        b.start();
        System.out.println("Thread-A 状态:" + a.getState());
        System.out.println("Thread-B 状态:" + b.getState());
        try {
            Thread.sleep(100000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程状态打印:
日志打印

jstack线程信息:jstack线程信息

WAITING状态

一、是什么

        /**
         * 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

翻译:

等待线程的线程状态。由于调用了以下方法之一,线程处于等待状态:①无超时等待的Object.wait;②无超时连接的Thread.join;③LockSupport.park
处于等待状态的线程正在等待另一个线程执行特定的操作。
例如,在一个对象上调用了object. wait()的线程正在等待另一个线程在该对象上调用object. notify()或object. notifyall();调用thread .join()的线程正在等待指定的线程终止。

二、什么情况下会进入

wait/notify/notifyAll

示例

 public static void testWait(){
        try{
            synchronized(object) {
                System.out.println("testWait-当前占用线程: " + Thread.currentThread().getName());
                object.wait();
                System.out.println("testWait被唤醒,执行业务代码");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void testNotify(){
        try{
            synchronized(object) {
                System.out.println("testNotify-当前占用线程: " + Thread.currentThread().getName());
                Thread.sleep(100000);
                System.out.println("testNotify唤醒wait");
                object.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread a = new Thread(()->{
            testWait();
        }, "Thread-A");
        Thread b = new Thread(()->{
            testNotify();
        }, "Thread-B");
        a.start();
        b.start();
        System.out.println("Thread-A 状态:" + a.getState());
        System.out.println("Thread-B 状态:" + b.getState());
        try {
            Thread.sleep(100000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

jstack线程信息

原理

Object.wait

    /**
     * Causes the current thread to wait until another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object.
     * In other words, this method behaves exactly as if it simply
     * performs the call {@code wait(0)}.
     * <p>
     * The current thread must own this object's monitor. The thread
     * releases ownership of this monitor and waits until another thread
     * notifies threads waiting on this object's monitor to wake up
     * either through a call to the {@code notify} method or the
     * {@code notifyAll} method. The thread then waits until it can
     * re-obtain ownership of the monitor and resumes execution.
     * <p>
     * As in the one argument version, interrupts and spurious wakeups are
     * possible, and this method should always be used in a loop:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait();
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of the object's monitor.
     * @throws  InterruptedException if any thread interrupted the
     *             current thread before or while the current thread
     *             was waiting for a notification.  The <i>interrupted
     *             status</i> of the current thread is cleared when
     *             this exception is thrown.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    public final void wait() throws InterruptedException {
        wait(0);
    }
Object.notify

    /**
     * Wakes up a single thread that is waiting on this object's
     * monitor. If any threads are waiting on this object, one of them
     * is chosen to be awakened. The choice is arbitrary and occurs at
     * the discretion of the implementation. A thread waits on an object's
     * monitor by calling one of the {@code wait} methods.
     * <p>
     * The awakened thread will not be able to proceed until the current
     * thread relinquishes the lock on this object. The awakened thread will
     * compete in the usual manner with any other threads that might be
     * actively competing to synchronize on this object; for example, the
     * awakened thread enjoys no reliable privilege or disadvantage in being
     * the next thread to lock this object.
     * <p>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. A thread becomes the owner of the
     * object's monitor in one of three ways:
     * <ul>
     * <li>By executing a synchronized instance method of that object.
     * <li>By executing the body of a {@code synchronized} statement
     *     that synchronizes on the object.
     * <li>For objects of type {@code Class,} by executing a
     *     synchronized static method of that class.
     * </ul>
     * <p>
     * Only one thread at a time can own an object's monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @see        java.lang.Object#notifyAll()
     * @see        java.lang.Object#wait()
     */
    public final native void notify();
Object.notifyAll

    /**
     * Wakes up all threads that are waiting on this object's monitor. A
     * thread waits on an object's monitor by calling one of the
     * {@code wait} methods.
     * <p>
     * The awakened threads will not be able to proceed until the current
     * thread relinquishes the lock on this object. The awakened threads
     * will compete in the usual manner with any other threads that might
     * be actively competing to synchronize on this object; for example,
     * the awakened threads enjoy no reliable privilege or disadvantage in
     * being the next thread to lock this object.
     * <p>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#wait()
     */
    public final native void notifyAll();

Thread.join

示例

public class JoinDemo {

    private static void testA() {
        System.out.println("testA执行");
        try {
           Thread.sleep(1000000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    private static void testB() {
        System.out.println("testB执行");
    }

    public static void main(String[] args) {
        Thread a = new Thread(()->{
            testA();
        }, "Thread-A");
        Thread b = new Thread(()->{
            testB();
            try {
                a.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Thread-B");
        a.start();
        b.start();
        try {
            Thread.sleep(6000);
        System.out.println("Thread-A 状态:" + a.getState());
        System.out.println("Thread-B 状态:" + b.getState());
         Thread.sleep(100000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

日志打印
jstack线程信息

原理

jstack中可以看出join的本质也是通过wait进行的处理。

LockSupport.park

示例

public class LockSDemo {

    public static void main(String[] args) {
        Thread a = new Thread(()->{
            LockSupport.park();
        }, "Thread-A");
        a.start();
        try {
            Thread.sleep(6000);
            System.out.println("Thread-A 状态:" + a.getState());
            Thread.sleep(100000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

日志打印:
日志打印
线程信息:
jstack线程信息

原理

LockSupport:java.util.concurrent.locks.LockSupport


    /**
     * Disables the current thread for thread scheduling purposes unless the
     * permit is available.
     *
     * <p>If the permit is available then it is consumed and the call
     * returns immediately; otherwise the current thread becomes disabled
     * for thread scheduling purposes and lies dormant until one of three
     * things happens:
     *
     * <ul>
     *
     * <li>Some other thread invokes {@link #unpark unpark} with the
     * current thread as the target; or
     *
     * <li>Some other thread {@linkplain Thread#interrupt interrupts}
     * the current thread; or
     *
     * <li>The call spuriously (that is, for no reason) returns.
     * </ul>
     *
     * <p>This method does <em>not</em> report which of these caused the
     * method to return. Callers should re-check the conditions which caused
     * the thread to park in the first place. Callers may also determine,
     * for example, the interrupt status of the thread upon return.
     */
    public static void park() {
        UNSAFE.park(false, 0L);
    }

翻译:

为线程调度目的禁用当前线程,除非许可可用。
如果许可证是可用的,那么它就会被使用,并且呼叫立即返回;否则,当前线程将被禁用,并处于休眠状态,直到发生以下三种情况之一:
其他一些线程以当前线程为目标调用unpark;或其他线程中断当前线程;或调用伪返回(也就是说,没有原因)。
此方法不报告其中哪些导致方法返回。调用者应该重新检查导致线程停止的条件。例如,调用者还可以确定线程返回时的中断状态。

那么,根据AQS源码以及说明描述中的** 当前线程 ,可以明白,unpark需要指定到 当前线程 才能唤醒park阻塞的 当前线程 **。

unpark(源码见注5)

    /**
     * Makes available the permit for the given thread, if it
     * was not already available.  If the thread was blocked on
     * {@code park} then it will unblock.  Otherwise, its next call
     * to {@code park} is guaranteed not to block. This operation
     * is not guaranteed to have any effect at all if the given
     * thread has not been started.
     *
     * @param thread the thread to unpark, or {@code null}, in which case
     *        this operation has no effect
     */
    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

翻译:

使给定线程的许可可用(如果它还不可用)。如果线程在park被阻塞,那么它将解除阻塞。否则,它的下一次调用park保证不会被阻塞。如果给定的线程没有启动,这个操作根本不能保证有任何效果。
参数:
线程——要取消驻留的线程,或者为null,在这种情况下,此操作不起作用

两个状态之间的区别(总结)

  1. BLOCKED是被动阻塞当前线程,WAITING是主动阻塞当前线程
  2. BLOCKED依赖synchroinzed,WAITING不依赖
  3. BLOCKED状态当锁释放后就会解除,但WAITING需要被唤醒或者等待超时后才会解除

扩展

Hashtable(synchroinzed)、ConcurrentHashMap(synchroinzed+cas)、condition(wait/notify)、reentrantlock(AQS-LockSupport.park/unpark)、JUC工具类(wait/notify)

参考文档:
注1:什么是monitor和Monitor监视器锁
注2:Monitor监视器锁原理
注3:synchronized底层原理
注4:wait/notify原理分析
注5:LockSupport.park/unpark


版权声明:本文为qq_39587013原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。