[小笔记] 理解 Java 中的 wait(),notify(),notifyAll() 等方法

[小笔记] 理解 Java 中的 wait(),notify(),notifyAll() 等方法

相信很多人都有去看过这几个方法的描述,站在面试八股文的角度可能自认为学会了,我感觉很多人并没有真正理解这几个方法,我自己也是经常忘记😂,所以这次单独记录一下,以方便以后自己忘记了再回来查看。

在理解上面的几个方法之前必须理解锁池和等待池的概念:

  • 锁池
    假如有一个对象 ObjectA,有三个线程 T1T2T3 都通过 synchronized 的方式去竞争锁,假如第一次竞争由 T2 线程获取了锁,那么 T1T3 就会被加入对象 ObjectA 的锁池,当 T2 线程释放锁之后,锁池里面的 T1T3 线程再重新竞争锁。简单来说就是对象的锁池里面的线程在持有锁的线程释放后他们会自动再去竞争锁。

  • 等待池
    假如线程 T2 通过 synchronized 的方式获取了 ObjectA 对象的锁后,T2 线程通过 ObjectA 对象的 wait() 方法(还会单独讲哈)释放对线对象ObjectA 的锁,并且会把 T2 线程添加到 ObjectA 对象的等待池中。等待池中的线程和锁池中的线程的区别是就算在对象 ObjectA 的锁被释放后也不会去主动去竞争锁。只有当等待池中的线程被移动到锁池后才会去重新竞争锁,通过 notify() 或者 notifyAll() (还会单独讲)方法可以把等待池中的线程移动到锁池中。

在理解了锁池和等待池的概念后我们再来聊 wait()notify()notifyAll() 这几个方法,他们都是由 Object 对象实现的。也就是说所有的其他对象也都可以使用这几个方法,这几个方法也都是必须在获取目标对象的 synchronized 锁之后才能够使用。

  • wait()
    在获取到对象的 synchronized 锁之后,通过调用 wait() 方法可以释放当前线程对对象的锁,同时当前线程会被加入到等待池中,当前的线程就会进入 WAITING 状态。只有等其他的线程调用 notify()/notifyAll() 方法后,这个线程会被移动到锁池中,在锁池中重新竞争获取到锁后才会把线程状态变为 RUNNING 状态,然后继续执行。
  • notify()
    在获取到对象的 synchronized 锁之后,通过调用 notify() 方法可以将等待池中的一个线程移动到锁池中(如果等待池为空,那就没有可移动的线程)。这个方法调用后不会释放对当前对象的锁,只有在当前线程对当前的锁释放后,锁池中的线程才重新竞争。
  • notifyAll()
    其他特性都和 notify() 方法类似,不过调用 notifyAll() 方法后可以将所有的等待池中的线程移动到锁池中。

写了这么多,再来看看几个验证的示例代码把。

wait()notify()

fun main(args: Array<String>) {

    val lock = Any() as Object

    val thread1 = Thread {
       synchronized(lock) {
           println("Thread1 got Lock.")
           Thread.sleep(150)
           lock.wait()
           println("Thread1 after wait")
       }
    }
    thread1.start()

    val thread2 = Thread {
        Thread.sleep(100)
        synchronized(lock) {
            println("Thread2 got Lock.")
            lock.notify()
            Thread.sleep(150)
            println("Thread2 after notify")
        }
    }
    thread2.start()

    thread1.join()
}

最终得到以下输出:

Thread1 got Lock.
Thread2 got Lock.
Thread2 after notify
Thread1 after wait

解释一下为什么是以上的输出:

首先在线程 thread2 开始前 sleep(100) 的目的是为了让线程 thread1 先获取到锁,所以先输出 Thread1 got Lock.

在线程 thread1 获取到锁后 sleep(150) 这时线程 thread2 已经开始竞争锁了,这时线程 thread1 调用 wait() 方法释放锁,同时把自己添加到等待池中,所以这个时候线程 thread2 获取到锁,所以输出 Thread2 got Lock.

在线程 thread2 获取到锁后调用 notify() 方法将线程 thread1 从等待池中移动到锁池中,由于这时 thread2 并没有释放锁虽然做了 sleep(150) 操作,所以还是会先输出 Thread2 after notify

在线程 thread2 释放锁后,由于在之前通过 notify() 方法将 thread1 从等待池中移动到锁池中,所以线程 thread1wait() 后重新获取到锁,最后输出 Thread1 after wait。(如果线程 thread2 中没有调用 notify() 方法,那么 thread1 会陷入无限的等待,永远不会输出 Thread1 after wait,读者可以自己试试)

notify()notifyAll()

fun main(args: Array<String>) {

    val lock = Any() as Object

    val thread1 = Thread {
       synchronized(lock) {
           println("Thread1 got Lock.")
           lock.wait()
           println("Thread1 after wait.")
       }
    }
    thread1.start()

    val thread2 = Thread {
        synchronized(lock) {
            println("Thread2 got Lock.")
            lock.wait()
            println("Thread2 after wait.")
        }
    }
    thread2.start()

    val thread3 = Thread {
        Thread.sleep(100)
        synchronized(lock) {
            println("Thread3 got Lock.")
            lock.notify()
            println("Thread3 after notify.")
        }
    }
    thread3.start()

    thread1.join()
}

最后输出是:

Thread1 got Lock.
Thread2 got Lock.
Thread3 got Lock.
Thread3 after notify.
Thread1 after wait.

解释一下上面你的输出:
这里故意让 thread3 sleep(100),后获取锁,这里 thread1thread2,会先获取到锁后会通过 wait() 方法释放锁,所以上面的输出是:

Thread1 got Lock.
Thread2 got Lock.

也可能是:

Thread2 got Lock.
Thread1 got Lock.

这取决于 thread1thread2 谁先竞争到锁。

由于 thread1thread2 都通过 wait() 释放锁并将其加入等待池,所以 thread3 获取到锁,所以最后的输出是:

Thread3 got Lock.

thread3 调用 notify() 后,会将等待池中的一个线程移动到锁池中,我的 demo 中移动的是 thread1,所以在线程 thread3 释放后 thread1wait() 方法后能够继续获取到锁,所以最后的输出是:

Thread3 after notify.
Thread1 after wait.

由于 thread2 没有被移动到锁池中去,所以他的 wait() 方法将永远等待下去,也就是常说的死锁。

我们为了不让 thread2 永远等下去,我们可以把 thread3 中的方法替换成 notifyAll() 方法:

fun main(args: Array<String>) {

    val lock = Any() as Object

    val thread1 = Thread {
       synchronized(lock) {
           println("Thread1 got Lock.")
           lock.wait()
           println("Thread1 after wait.")
       }
    }
    thread1.start()

    val thread2 = Thread {
        synchronized(lock) {
            println("Thread2 got Lock.")
            lock.wait()
            println("Thread2 after wait.")
        }
    }
    thread2.start()

    val thread3 = Thread {
        Thread.sleep(100)
        synchronized(lock) {
            println("Thread3 got Lock.")
            lock.notifyAll()
            println("Thread3 after notifyAll.")
        }
    }
    thread3.start()

    thread1.join()
}

最后就的输出就是:

Thread1 got Lock.
Thread2 got Lock.
Thread3 got Lock.
Thread3 after notifyAll.
Thread1 after wait.
Thread2 after wait.

小结

到这里相信你就对 wait()notify()notifyAll() 的理解就要深一点了,面试八股文和实战都能够做到游刃有余。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值