一、线程间通信的两种方式
1.wait()/notify()
Object类中相关的方法有notify方法和wait方法。因为wait和notify方法定义在Object类中,因此会被所有的类所继承。这些方法都是final的,即它们都是不能被重写的,不能通过子类覆写去改变它们的行为。
①wait()方法: 让当前线程进入等待,并释放锁。
②wait(long)方法: 让当前线程进入等待,并释放锁,不过等待时间为long,超过这个时间没有对当前线程进行唤醒,将自动唤醒。
③notify()方法: 让当前线程通知那些处于等待状态的线程,当前线程执行完毕后释放锁,并从其他线程中唤醒其中一个继续执行。
④notifyAll()方法: 让当前线程通知那些处于等待状态的线程,当前线程执行完毕后释放锁,将唤醒所有等待状态的线程。
wait()方法使用注意事项
①当前的线程必须拥有当前对象的monitor,也即lock,就是锁,才能调用wait()方法,否则将抛出异常java.lang.IllegalMonitorStateException。
②线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。
③要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。
wait()与sleep()比较
当线程调用了wait()方法时,它会释放掉对象的锁。
Thread.sleep(),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。
notify()方法使用注意事项
①如果多个线程在等待,它们中的一个将会选择被唤醒。这种选择是随意的,和具体实现有关。(线程等待一个对象的锁是由于调用了wait()方法)。
②被唤醒的线程是不能被执行的,需要等到当前线程放弃这个对象的锁,当前线程会在方法执行完毕后释放锁。
wait()/notify()协作的两个注意事项
①通知过早
如果通知过早,则会打乱程序的运行逻辑。
如果notify()方法先执行,将导致wait()方法释放锁进入等待状态后,永远无法被唤醒,影响程序逻辑。应避免这种情况。
②等待wait的条件发生变化
在使用wait/notify模式时,还需要注意另外一种情况,也就是wait等待条件发生了变化,也容易造成程序逻辑的混乱。
二、生产者/消费者模式实现
1.一生产与一消费
下面情形是一个生产者,一个消费者的模式。假设场景:一个String对象,其中生产者为其设置值,消费者拿走其中的值,不断的循环往复,实现生产者/消费者的情形。
2.多生产与多消费
特殊情况: 按照上述一生产与一消费的情况,通过创建多个生产者和消费者线程,实现多生产与多消费的情况,将会出现“假死”。
具体原因: 多个生产者和消费者线程。当全部运行后,生产者线程生产数据后,可能唤醒的同类即生产者线程。此时可能会出现如下情况:所有生产者线程进入等待状态,然后消费者线程消费完数据后,再次唤醒的还是消费者线程,直至所有消费者线程都进入等待状态,此时将进入“假死”。
解决方法: 将notify()或signal()方法改为notifyAll()或signalAll()方法,这样就不怕因为唤醒同类而进入“假死”状态了。