如果没有通知等待机制,则只能让线程使用while(true)
死循环,来一直执行。不断的进行条件判断,等到符合条件便自动退出。但这样线程便一直执行(轮询),会浪费CPU资源。
由此,引入等待/通知机制(原理不过说明)。
wait()使线程停止运行,notify()使停止的线程继续运行。
wait()方法:将当前线程置于“预执行队列”,并在wait()所在的代码行处停止执行,直到接到通知或者被中断为止。
使用注意:
调用wait()
之前:线程必须获得该对象的对象级别锁(即只能在同步方法或同步代码块中调用wait()方法)
调用wait()
时:如没有持有适当的锁,则抛出IllegalMonitorStateException(RunTimeException的一个子类,无需try/catch)
执行wait()
之后:当前线程释放锁
从wait()
返回前(即调用notify()之后):线程与其他线程竞争重新获得锁。
方法notify():用来通知那些可能等待该对象的对象锁的其他线程。如有多个线程等待,则由线程规划器随机挑选一个呈wait状态的线程,对其发出通知notify,并使他等待获取该对象的对象锁。
使用注意:
调用notify()
之前:线程必须获得该对象的对象级别锁(即只能在同步方法或同步代码块中调用notify()
方法)
调用notify()
时:如没有持有适当的锁,则抛出IllegalMonitorStateException(RunTimeException的一个子类,无需try/catch)
执行notify()
之后:
notify一次只能通知一个线程,而每个wait的线程都只有被noyify之后才会执行。
wait() | notify() |
---|---|
调用前 | 必须获得该对象的对象级别锁 |
调用时 | 没有持有适当的锁,则抛异常 |
执行后(等待被 notify()唤醒时)+锁释放 |
当前线程立马释放锁; 线程从运行状态退出,进入阻塞状态,进入等待队列直到被再次唤醒 |
被notify()唤醒后 | 线程进入就需状态,重新尝试获取对象锁,并执行wait后续代码 |
当线程wait状态时,调用线程的interrupt()方法会出现InterruptedException异常。(该异常由wait方法抛出。其实遇到sleep方法和join方法同样抛异常)
更多理解可参考:阻塞(sleep等等)区别 中断(interrupt)+ 中断的意义 - baoendemao - 博客园 https://www.cnblogs.com/baoendemao/p/3804730.html
wait(long)和sleep的原理很像,到期自动唤醒,相当于到期自动执行一个notify。未到期之前也可被其他notify唤醒。
方法定义:等待线程对象销毁。(即当线程销毁之后,执行的线程继续执行)
实例解释:是所属的线程对象x正常执行run()方法中的任务,而当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。
作用:可使线程排队运行的作用,有类似同步的运行效果(synchronized)。
join | synchronized |
---|---|
区别 | 内部使用wait等待 |
如果一个线程z在等待另一个x的join,忽然线程z调用了interrupt,那么线程z会出现异常。
但线程x还在继续执行,因为线程x没有出现异常。
理解:join本质是wait,wait遇到interrupt会抛出异常。
join(long)的理解
说明:方法join(long)中的参数是设定等待的时间。(和sleep很像)
即使线程x需要执行很久,但是只要join(long)时间到了,线程z就会继续往下执行。
方法 join(long):内部使用wait(long)实现,所以其会释放(当前线程持有的)锁。
而sleep(long):并不会释放锁。
这个例子说了一个问题:
因为不确定,所以可能会有问题。
原理和List一样,不过对于管道输入流来说,其自带的read方法,如果读取不到数据,就会自己阻塞。无需像list那些需自行让线程wait
和上一个没有太多区别,只是这个是字符流,上一个是字节流。
只是让两个线程交替执行而已,使用一个boolean变量作为开关进行控制,没太多需要说明。
正常模式:生产1个→消费1个→生产1个→消费1个→生产1个→消费1个;
消费异常模式:生产1个→消费1个→再消费一个(无法消费,自己阻塞。然后只能等待生产者生产后将自己唤醒)→.......→生产1个→消费1个→生产1个→消费1个;
生产异常模式:生产1个→消费1个→生产1个→再生产一个(无法生产,自己阻塞。然后只能等待消费者消费后将自己唤醒)→.......→消费1个→生产1个→消费1个;
注意:一直只有一个阻塞,所以无需担心notify被错误消费;
根据值进行控制判断(什么时候进入阻塞状态)
总结:
假死实际不是很理解...不过知道了原因,是因为notify唤醒了不该唤醒的wait,导致notify被错误消费(消费之后应再有一个notify,错误消费之后就没有了),然后后续逻辑错误,因此假死。
解决上述假死:将notify换为notifyAll
根据list的size进行控制判断(什么时候进入阻塞状态)
多个消费者,都处于阻塞;
如果一个消费者消费之后,执行notify(notify是随机唤醒),而该notify被另一消费者使用,另一消费者直接往下执行(不进行while的额外一重判断),直接执行后面,导致异常。
while可以解决,因为while和if不一样。while那么肯定会再一次判断,判断发现是0,然后自己阻塞(即notify被浪费)了,然后导致了假死....
当被notify时:
notifyAll肯定会唤醒生产者,生产者肯定会生产一个,然后继续消费,一直循环下去,肯定不会阻塞。
这个好像没什么问题
这个好像也没什么问题
所有线程共享同一个变量情况:public static
每个线程都有自己的共享变量:使用ThreadLocal(主要解决:每个线程绑定自己的值,可以将其理解为全局存放数据的盒子,盒子中可以存放每个线程的私有数据)
get()第一次调用会返回null(看源码:因为ThreadLocal的initialValue()方法返回的就是null,即每次初始化为null),除非进行set()的操作
ThreadLocal(public static)只有一个,但每个线程只可以放入自己的值,取值的时候只会取出来自己的值,这个好像是代码自己实现的。我操,这才是ThreadLocal的牛掰之处。
为什么会这样?可以看下ThreadLocal的get和set方法。里面每次都会获取当前线程,然后再进行后续逻辑。内部是一个 ThreadLocalMap。
使用InheritableThreadLocal可以在子线程中取得(子线程中取的是父线程的值,自己没有相关值)父线程继承下来的值。
子线程可以覆盖父线程的childValue()方法,对主线程的值进行额外处理。
注意:如果子线程取值时,主线程将InheritableThreadLocal中的值进行了修改,那么子线程取到的还是旧值。
新建之后 | 可运行状态(从运行状态变为可运行状态) | 运行状态 | 阻塞状态 | 销毁状态 |
---|---|---|---|---|
|
|
|
|
|
|
|
|||
|
|
|||
4.wait线程收到其他线程发出的notify通知 |
|
|||
|
|
|||
|
start() 和run() | start(): 线程准备执行,具体执行由线程调度器决定 |
yield()和sleep() | yield(): 停止当前正在执行的线程,释放当前锁,让同样优先级的正在等待的线程有机会执行(只是有机会,具体怎么执行看系统,也可能还是自己执行) |
suspend()和resume() | suspend(): 使当前线程阻塞,不释放对象锁,只能被resume()恢复。 |
wait()和notify()和notifyAll() | wait(): 释放当前锁,等待被notify()通知 |
stop() | 停止线程,强制停止,不安全。 |
interrupt()和interrupted()和isInterrupted() | 中断线程。 调用该方法的线程的状态为将被置为"中断"状态。 并非真的立即停止。 更深的理解参考:阻塞(sleep等等)区别 中断(interrupt)+ 中断的意义 - baoendemao - 博客园 https://www.cnblogs.com/baoendemao/p/3804730.html |
每个锁对象都有两个对列,就绪队列和阻塞队列。
就绪队列:存储将要获得锁的线程。(线程被唤醒后才会进入就需队列,等待CPU的调度)
阻塞队列:存储了被阻塞的线程。(线程被wait之后,就会进入阻塞队列,等待下一次被唤醒)
原文:https://www.cnblogs.com/buwuliao/p/9538260.html