notify()真的是随机唤醒嘛?

notify()的唤醒机制


前言

学过java的小伙伴都知道,我们在学习过程中,在学习并发多线程时,有一个notify()方法,当时博主在学习的时候,是说notify()随机唤醒一个等待中的线程并获取锁。相信许多小伙伴和博主一样,但是事实真的是这样吗?

notify()

先上一段简单的线程wait()与notify()方法。方法的逻辑就不用我讲了吧。。。

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class Main {

    private static List<String> waitList = new LinkedList<>();
    private static List<String> notifyList = new LinkedList<>();
    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 50; i++) {
            String threadName = String.valueOf(i);
            new Thread(() -> {
                synchronized (lock) {
                    String cthreadNmae = Thread.currentThread().getName();
                    System.out.println("线程:" + cthreadNmae + "正在等待");
                    waitList.add(cthreadNmae);
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程:" + cthreadNmae + "被唤醒");
                    notifyList.add(cthreadNmae);
                }
            }, threadName).start();
            TimeUnit.MILLISECONDS.sleep(50);
        }
        TimeUnit.SECONDS.sleep(1);
        for (int i = 1; i <= 50; i++) {
            synchronized (lock) {
                lock.notify();
                TimeUnit.MILLISECONDS.sleep(10);
            }
            
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println(waitList.toString());
        System.out.println(notifyList.toString());
    }


}

运行以上代码:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]
[1, 2, 5, 4, 3, 11, 10, 9, 8, 7, 6, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 50, 49]

卧槽,notify()好像真的是随机唤醒的,就在这个时候我手贱了一下,对代码进行了一下修正:

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class Main {

    private static List<String> waitList = new LinkedList<>();
    private static List<String> notifyList = new LinkedList<>();
    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <= 50; i++) {
            String threadName = String.valueOf(i);
            new Thread(() -> {
                synchronized (lock) {
                    String cthreadNmae = Thread.currentThread().getName();
                    System.out.println("线程:" + cthreadNmae + "正在等待");
                    waitList.add(cthreadNmae);
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程:" + cthreadNmae + "被唤醒");
                    notifyList.add(cthreadNmae);
                }
            }, threadName).start();
            TimeUnit.MILLISECONDS.sleep(50);
        }
        TimeUnit.SECONDS.sleep(1);
        for (int i = 1; i <= 50; i++) {
            synchronized (lock) {
                lock.notify();
                
            }
            TimeUnit.MILLISECONDS.sleep(10);
        }
        TimeUnit.SECONDS.sleep(1);
        System.out.println(waitList.toString());
        System.out.println(notifyList.toString());
    }


}

再去运行代码:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]

卧槽,这到底怎么回事?改动了一行代码,notify()竟然有序了?
好奇心爆棚的我决定研究下去,经过一番深思熟虑(抓耳挠腮)的思考之后,发现问题出在了sleep()上。

 for (int i = 1; i <= 50; i++) {
            synchronized (lock) {
                lock.notify();
                TimeUnit.MILLISECONDS.sleep(10);
            }
        }

当notify()之后,等待的线程A被唤醒等待获取锁,但是sleep()了,此时循环中的synchronized()代码块也需要获取锁,假设此时代码块获取锁,那等待的线程A进入“锁池”等待,又唤醒一个等待线程B,此时锁池有A、B两条等待线程,假设B获取到了锁,此时唤醒的顺序看来就是无序的,如果把sleep()移出去,让同步代码块暂缓拿锁,那唤醒的就是有序的啦。

为什么这样呢?
看一下notify()的源码:

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

这段注释的意思大概是:

**notify在源码的注释中说到notify选择唤醒的线程是任意的,但是依赖于具体实现的jvm.**

小伙伴们都知道,jvm实现方式有很多,最主流的就是HotsPot。
看一下在HotsPot中对notify()的实现:
在这里插入图片描述
synchronized的wait()方法和notify()方法是位于ObjectMonitor中。而notify()过程调用的是DequeueWaiter方法:
在这里插入图片描述
实际上是将等待队列中的第一个节点出队并获取锁,此时也就不难理解为什么notify()是有序的啦!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值