多线程(三)
上一期关于线程的执行,咱们说到线程是 “ 随机调度,抢占式执行 ”。所以我们对于线程之间执行的先后顺序是难以预知的。
例如咱们打篮球的时候,球场上的每一位运动员都是一个独立的 “ 执行流 ”,也可以认为是一个线程,进球就是咱们要执行的 “ 任务 ”,我们打球肯定不是说每个球员都想着抢球投球,要想赢得一场漂亮的比赛,咱们肯定是要执行战术的,每个人都有自己的位置,什么时候谁持球做什么,这都是很严谨的。只有多位运动员相互配合,按照一定顺序打出进攻防守,这样才能赢球。
线程是 “ 天生 ” 就这样互相抢占式执行的,但是我们难道就放任线程之间这样子随意执行不管嘛,当然不是了,有些时候在特定的场景下,咱们对实现线程的调度执行是有一定要求的。而这就引入了我们的 wait(等待) 和 notify(通知)。
(1)wait()/ wait(long timeout):让当前线程进入等待状态。
(2)notify()/ notifyAll():唤醒在当前对象上等待的线程 / 唤醒全部线程。
注意:wait 和 notify 、notifyAll 都是 Object 类的方法。
一 . wait(等待)
wait 要做的事:
(1)使当前执行代码的线程进行等待(将线程放到等待队列中)。
(2)释放当前的锁。
(3)满足一定条件时被唤醒,重新尝试获取这个锁。
注意:wait 一定要搭配 synchornized 使用,不然会直接抛出异常。
wait 结束等待的条件:
(1)其他线程调用该对象的 notify 方法。
(2)当使用 wait 的带参数版本时,等待超时自动唤醒。
(3)其他线程调用该线程的 interrupted 方法,会导致 wait 抛出 InterruptedException 异常。
咱们通过代码来看一下:
大家可以看出来,当我们使用无参数版本的 wait 时,若是我们不将其唤醒,那么这个线程就会一直等待下去,直到被唤醒为止。
wait 默认是 “死等 ”, 这里我们再使用 wait 的带参数版本,看一下是否会自动唤醒 wait 。
如图所示,经过咱们等待前后的时间差可以看出 wait 确实是等待了 3000 ms 还没有等待人来唤醒它,这时候它就不等了,线程直接继续执行。
二 . notify(通知)
notify 要做的事:
(1)唤醒 wait 等待的线程。
(2)如果有多个线程等待,则由线程调度器随机唤醒其中的一个呈 wait 状态的线程。
(3)notify 同样要配合 synchornized 使用。
(4)在 notify()方法之后,当前线程不会立马释放该对象锁,而是要等待执行 notify()方法的线程将程序执行完,也就是退出同步代码块后才会释放对象锁。
大家通过下列代码看一下,wait 和 notify 配合 synchornized 使用:
notifyAll 跟 notify 的区别就在于:
(1)notify 是随机唤醒等待队列中的一个线程,其余还有的线程还得乖乖等着。那么我们还想要继续唤醒剩下的线程怎么办呢?这个时候有小伙伴就灵机一动了,我们多唤醒几次,多调用几次 notify 不就行了吗?NONONO,这样可不行,要想唤醒剩下的线程,我们得在各线程内部进行 wait 和 notify 的配合使用才行。
接下来我们来看这样一个示例,按照顺序打印 A、B、C :
在这里再一次强调,我们线程的执行是 “ 随机调度,抢占式执行 ”,所以这里打印A、B、C的顺序我们是无法预知的,如下图所示。
这个时候咱们就需要 wait 和 notify 配合使用,并且我们需要两把锁。
此时我们不管经过多少次试验,结果都是我们预期的 A、B、C 顺序。
注意:这里我们在线程 t1 中为什么要强制等待一个 10 ms 呢?如果我们不加这个强制等待就会发现,当我们执行代码的时候,只能打印出 A ,然后程序就不动了。这是为什么呢?这是因为大概率存在这样的情况:我们的 t1 先执行了打印和 notify ,执行的很快,然后我们的 t2 才执行 wait ,这就意味着 t1 的通知过早了,这样就会造成我们的 t2 一直等下去,因为属于它的通知已经错过了。
这就好比如一个悲伤的故事,在学生时代,有一对男孩女孩互生情愫,但都是暗恋,等到即将毕业前,女孩在送给男孩的一本书里偷偷夹了一封信,已示告白,但是粗心大意的男生并没有发现,直到多年后,男孩偶然间翻开这本书,这才发现这封已经泛黄的告白信,但此刻时光荏苒,物是人非事事休咯,这就是错失的遗憾。
这个故事是我在听老师讲课的时候,老师举的例子,但是很不幸的是,我也有着一段这样的经历,不是百分百一样,但也所差无几。
缘分就像一本书,翻得太快会错过,读的认真又流泪。所以各位,有时候冲动一点不是坏事,大家在该勇敢的时候还是要牢牢地抓出那个他 / 她,忘诸君不留遗憾。
(2)notifyAll 是一下将所有等待队列中的线程全部唤醒,但是,这些锁会重新竞争。notifyAll 很简单,咱们就不在这儿演示了。
三 . wait 和 sleep 的区别
理论上来说,wait 和 sleep 是没有任何可比性的,因为这两个概念虽然实现的作用大致相同,都可以让线程放弃执行一段时间,但是并不是一个性质的。
(1)wait 是用于线程之间通信的,sleep 是让线程阻塞一段时间。
(2)wait 需要搭配 synchornized 使用,sleep 不需要,直接通过 Thread 调用即可。
(3)wait 是 Object 的方法,sleep 是 Thread 的静态方法。
(4)sleep 与锁无关,不加锁,sleep 可以正常使用;加了锁,sleep 操作,不会释放锁,相当于在 sleep “ 休眠 ” 的这段时间内,它就是抱着锁睡得,那么其余线程都拿不到锁。
OKK,今天就说这么多了,这一期是专门讲解 wait 和 notify 的,大概就这些了。咱们下期再见吧,与诸君共勉!!!