1.通信
就是指相互交换一些数据或者发送一些控制指令,比如一个线程给另一个暂停执行的线程发送一个恢复执行的指令。
可变共享变量是天然的通信媒介,也就是说一个线程如果想和另一个线程通信的话,可以修改某个在多线程间共享的变量,另一个线程通过读取这个共享变量来获取通信的内容。
2.这里边就不得不提wait/notify
机制了:
当一个线程获取到锁之后,如果发现条件1不满足,那就主动让出锁(ps:wait使当前线程让出对象锁x1,即x1.wait()),然后把这个线程放到一个等待队列
里等待
去,等到其他某个线程把这个条件1变成ture之后,就通知
等待队列里的线程他们等待的条件满足了,可以继续运行啦(ps:通知某个等待线程竞争到锁之后,进入就绪状态,等待时间片再运行)!
java里规定了每一个锁都对应了一个等待队列
,也就是说如果一个线程在获取到锁之后发现某个条件不满足,就主动让出锁然后把这个线程放到与它获取到的锁对应的那个等待队列里,另一个线程在完成对应条件时需要获取同一个锁,在条件完成后通知它获取的锁对应的等待队列(ps:对象锁x1.notify())
。这个过程意味着锁和等待队列建立了一对一关联。
3.代码格式如下:
//wait格式
synchronized (对象) { 处理逻辑(可选) while(条件不满足) { 对象.wait(); } 处理逻辑(可选) }
-
获取对象锁。
-
如果某个条件不满足的话,调用锁对象的
wait
方法,被通知后仍要检查条件是否满足。 -
条件满足则继续执行代码
//notify格式
synchronized (对象) {
完成条件
对象.notifyAll();、
}
静态同步方法的锁对象可以是该类的`Class对象`,成员同步方法的锁对象可以是`this对象`。
-
获得对象的锁。
-
完成条件。
-
通知在等待队列中的等待线程。
4.误区分析
误区-1:为什么必须在同步代码块中调用wait
、 notify
或者notifyAll
方法。
wait和notify作用于多个线程,彼此之间是互斥的,使用同步确保原子性操作,如果不使用的话,可能会出现如下执行情况
也就是说当等待线程已经判断条件不满足,正要执行wait
方法,此时通知线程抢先把条件完成并且调用了notify
方法,之后等待线程才执行到wait
方法,这会导致等待线程永远停留在等待队列而没有人再去notify
它。所以等待线程中的判断条件是否满足、调用wait方法和通知线程中完成条件、调用notify方法都应该是原子性操作,彼此之间是互斥的,所以用同一个锁来对这两个原子性操作进行同步,从而避免出现等待线程永久等待的尴尬局面。
另外不加锁使用的话,对象.wait();
运行会抛出IllegalMonitorStateException异常。
并且不能随便调用某个对象的wait,对象一定要跟同步对象一致:
synchronized (对象1) { while(条件不满足) { 对象2.wait(); //随便调用一个对象的wait方法 } } synchronized (对象1) { 完成条件 对象2.notifyAll(); } 对于代码对象2.wait(),表示让出当前线程持有的对象2的锁,而当前线程持有的是对象1的锁,所以这么写是错误的,也会抛出IllegalMonitorStateException异常的。意思就是如果当前线程不持有某个对象的锁,那它就不能调用该对象的wait方法来让出该锁。所以如果想让等待线程让出当前持有的锁,只能调用对象1.wait()。然后这个线程就被放置到与对象1相关联的等待队列中,在通知线程中只能调用对象1.notifyAll()来通知这些等待的线程了。
误区2-在等待线程判断条件是否满足时,应该使用while
,而不是if。
这个是因为在多线程条件下,可能在一个线程调用notify
之后立即又有一个线程把条件改成了不满足的状态,之后调用wait的线程获得cpu和锁,开始执行,当前条件是不满足的,肯定不能往下执行,还需要再 走一次while条件验证,满足条件才继续往下执行,不满足的话继续wait。并且整个同步代码块执行完,才会释放锁,单独调用notify是不会释放锁的。