一道 Java 的题目:

    关于 sleep () 和 wait (),以下描述错误的一项是:

    • A sleep 是线程类(Thread)的方法,wait 是 Object 类的方法;
    • B sleep 不释放对象锁,wait 放弃对象锁
    • C sleep 暂停线程、但监控状态仍然保持,结束后会自动恢复
    • D wait 后进入等待锁定池,只有针对此对象发出 notify 方法后获得对象锁进入运行状态
    1. 关于对象锁:

    截取网上的一段话:

    所有对象都自动含有单一的锁。
    JVM 负责跟踪对象被加锁的次数。如果一个对象被解锁,其计数变为 0。在任务(线程)第一次给对象加锁的时候,计数变为 1。每当这个相同的任务(线程)在此对象上获得锁时,计数会递增。
    只有首先获得锁的任务(线程)才能继续获取该对象上的多个锁。
    每当任务离开一个 synchronized(同步)方法,计数递减,当计数为 0 的时候,锁被完全释放,此时别的任务就可以使用此资源。

    这段话令人感到迷惑,一个对象不是只有一个锁吗?只有获得这个对象的锁才能对它进行操作,若这个对象的锁被一个线程先获得,那就其他线程就需要等待。那多次加锁什么意思,锁不是依附于对象的吗?
    在往下的文章中,我暂且理解为一个对象有且只有一把锁,锁在不同线程间传递,一个线程可以多次获得同一个对象的锁。暂且不考虑一个对象上多个锁这种方法是不是确实存在,这对下面影响不大。

    1. 关于锁池和等待池

    在 Java 中,每个对象都有两个池,锁 (monitor) 池和等待池

    锁池:假设线程 A 已经拥有了某个对象 (注意:不是类) 的锁,而其它的线程想要调用这个对象的某个 synchronized 方法 (或者 synchronized 块),由于这些线程在进入对象的 synchronized 方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程 A 拥有,所以这些线程就进入了该对象的锁池中。

    等待池:假设一个线程 A 调用了某个对象的 wait () 方法,线程 A 就会释放该对象的锁 (因为 wait () 方法必须出现在 synchronized 中,这样自然在执行 wait () 方法之前线程 A 就已经拥有了该对象的锁),同时线程 A 就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的 notifyAll () 方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的 notify () 方法,那么仅仅有一个处于该对象的等待池中的线程 (随机) 会进入该对象的锁池.

    深入理解:
    如果线程调用了对象的 wait () 方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
    当有线程调用了对象的 notifyAll () 方法(唤醒所有 wait 线程)或 notify () 方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
    优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait () 方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

    注:wait () ,notifyAll (),notify () 三个方法都是 Object 类中的方法.

    1. 关于 wait () ,notifyAll (),notify () 三个方法

    wait()
    public final void wait() throws InterruptedException,IllegalMonitorStateException
    该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用 wait () 之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait () 方法。进入 wait () 方法后,当前线程释放锁。在从 wait () 返回前,线程与其他线程竞争重新获得锁。如果调用 wait () 时,没有持有适当的锁,则抛出 IllegalMonitorStateException,它是 RuntimeException 的一个子类,因此,不需要 try-catch 结构。

    notify()
    public final native void notify() throws IllegalMonitorStateException
    该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用 notify () 时没有持有适当的锁,也会抛出 IllegalMonitorStateException。

    该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个 wait () 状态的线程来发出通知,并使它等待获取该对象的对象锁(notify 后,当前线程不会马上释放该对象锁,wait 所在的线程并不能马上获取该对象锁,要等到程序退出 synchronized 代码块后,当前线程才会释放锁,wait 所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象 notify 的线程们。当第一个获得了该对象锁的 wait 线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用 notify 语句,则即便该对象已经空闲,其他 wait 状态等待的线程由于没有得到该对象的通知,会继续阻塞在 wait 状态,直到这个对象发出一个 notify 或 notifyAll。这里需要注意:它们等待的是被 notify 或 notifyAll,而不是锁。这与下面的 notifyAll () 方法执行后的情况不同。

    notifyAll()
    public final native void notifyAll() throws IllegalMonitorStateException
    该方法与 notify () 方法的工作方式相同,重要的一点差异是:

    notifyAll 使所有原来在该对象上 wait 的线程统统退出 wait 的状态(即全部被唤醒,不再等待 notify 或 notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll 线程退出调用了 notifyAll 的 synchronized 代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。

    4.sleep () 不会释放掉锁(监控)
    最开始的那道题答案是 D
    ————————————————
    版权声明:本文为CSDN博主「我就是小在」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u014561933/article/details/58639411