JAVA线程的synchronized、wait、notify、notifyall如何配合工作
概念:锁池、等待池、同步、资源锁、等待、唤醒 流程描述: 1.资源锁:多线程场景下的公共资源,资源锁对象有锁池和等待池两个区域 3.锁池,存放等待资源锁的线程 4.等待池,存放wait状态的线程,等待池中的线程不参与锁竞争。 5.线程得到资源锁后开始工作,可以通过wait方法临时释放锁进入等待池 6.等待池中的线程只有被唤醒才能重新竞争锁,如果竞争到锁,接着之前的点继续干活讲故事:
1.病人到医院交了挂号费,进入候诊区排队等着医生看病。这里医生就是公共资源,候诊区就是医生的锁池。 2.病人进入诊室让医生看病。这里就是从锁池中竞争到了锁,线程可以开始干活。 3.医生看了看病人的情况,告诉病人你这病只能我师父给看,我先做个登记,你回家等着吧,等我师傅来了我通知你,病人就离开了诊室。这里医生调用了wait方法让病人等待,病人的家就是医生的等待池,病人离开诊室就是释放了锁,病人接到医生通知后要重新到诊室排队。如果调用了wait(day)方法让病人等待,就是告诉病人几天后再来。 4.医生的师傅回来了,师傅对医生说,你让之前等我的那些人都来吧。这里医生调用了notifyall方法,等待在家里的所有病人就到医院候诊区排队,谁先排上队看病人的本事。 5.医生的师傅回来了,师傅对医生说,你随机挑一个病人来吧。这里医生调用了notify方法,等待在家里病人其中一个运气比较好被选中了,他就到候诊区来排队了。注:
1.线程本身属性:sleep、join、yield方法
2.多线程同步使用的Object属性:wait、notify、notifyAll是Object的方法(synchronized要针对资源object起作用,所以也要通过调用资源object的wait和notify方法实现锁的等待),结论wait和notify以及notifyall必须要在synchronized代码块中使用,否则Java虚拟机会生成 IllegalMonitorStateException。因为wait的目的是为了进入等待并释放锁。
3.在多线程同步的时候使用等待池和锁池
4.在单线程运行的时候可以使用sleep、yield释放CPU控制权,进去就绪池
5.join是一个特例,thread.join()是要当前线程等待,直到thread运行完成。thread.join就是让“当前线程”(正在拥有CPU控制权的主线程)等待,thread线程运行结束时会通过native方法调用notify让主线程继续工作。
实例:
Java wait(), notify(), notifyAll() 范例
下面我们提供一个使用wait和notify的范例程序。在这个程序里,我们使用了上文所述的一些代码规范。我们有两个线程,分别名为PRODUCER(生产者)和CONSUMER(消费者),他们分别继承了了Producer和Consumer类,而Producer和Consumer都继承了Thread类。Producer和Consumer想要实现的代码逻辑都在run()函数内。Main线程开始了生产者和消费者线程,并声明了一个LinkedList作为缓冲区队列(在Java中,LinkedList实现了队列的接口)。生产者在无限循环中持续往LinkedList里插入随机整数直到LinkedList满。我们在while(queue.size == maxSize)循环语句中检查这个条件。请注意到我们在做这个检查条件之前已经在队列对象上使用了synchronized关键词,因而其它线程不能在我们检查条件时改变这个队列。如果队列满了,那么PRODUCER线程会在CONSUMER线程消耗掉队列里的任意一个整数,并用notify来通知PRODUCER线程之前持续等待。在我们的例子中,wait和notify都是使用在同一个共享对象上的。
import java.util.LinkedList;import java.util.Queue;import java.util.Random;/*** Simple Java program to demonstrate How to use wait, notify and notifyAll()* method in Java by solving producer consumer problem.** @author Javin Paul*/public class ProducerConsumerInJava { public static void main(String args[]) { System.out.println("How to use wait and notify method in Java"); System.out.println("Solving Producer Consumper Problem"); Queue<Integer> buffer = new LinkedList<>(); int maxSize = 10; Thread producer = new Producer(buffer, maxSize, "PRODUCER"); Thread consumer = new Consumer(buffer, maxSize, "CONSUMER"); producer.start(); consumer.start(); } } /** * Producer Thread will keep producing values for Consumer * to consumer. It will use wait() method when Queue is full * and use notify() method to send notification to Consumer * Thread. * * @author WINDOWS 8 * */ class Producer extends Thread { private Queue<Integer> queue; private int maxSize; public Producer(Queue<Integer> queue, int maxSize, String name){ super(name); this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (queue) { while (queue.size() == maxSize) { try { System.out .println("Queue is full, " + "Producer thread waiting for " + "consumer to take something from queue"); queue.wait(); } catch (Exception ex) { ex.printStackTrace(); } } Random random = new Random(); int i = random.nextInt(); System.out.println("Producing value : " + i); queue.add(i); queue.notifyAll(); } } } } /** * Consumer Thread will consumer values form shared queue. * It will also use wait() method to wait if queue is * empty. It will also use notify method to send * notification to producer thread after consuming values * from queue. * * @author WINDOWS 8 * */ class Consumer extends Thread { private Queue<Integer> queue; private int maxSize; public Consumer(Queue<Integer> queue, int maxSize, String name){ super(name); this.queue = queue; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (queue) { while (queue.isEmpty()) { System.out.println("Queue is empty," + "Consumer thread is waiting" + " for producer thread to put something in queue"); try { queue.wait(); } catch (Exception ex) { ex.printStackTrace(); } } System.out.println("Consuming value : " + queue.remove()); queue.notifyAll(); } } } }
为了更好地理解这个程序,我建议你在debug模式里跑这个程序。一旦你在debug模式下启动程序,它会停止在PRODUCER或者CONSUMER线程上,取决于哪个线程占据了CPU。因为两个线程都有wait()的条件,它们一定会停止,然后你就可以跑这个程序然后看发生什么了(很有可能它就会输出我们以上展示的内容)。你也可以使用Eclipse里的Step into和Step over按钮来更好地理解多线程间发生的事情。
notify()和notifyAll()的本质区别
notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。两者的最大区别在于:
notifyAll使所有原来在该对象上等待被notify的所有线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
notify则文明得多,它只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。本文重点:
1. 你可以使用wait和notify函数来实现线程间通信。你可以用它们来实现多线程(>3)之间的通信。
2. 永远在synchronized的函数或对象里使用wait、notify和notifyAll,不然Java虚拟机会生成 IllegalMonitorStateException。
3. 永远在while循环里而不是if语句下使用wait。这样,循环会在线程睡眠前后都检查wait的条件,并在条件实际上并未改变的情况下处理唤醒通知。
4. 永远在多线程间共享的对象(在生产者消费者模型里即缓冲区队列)上使用wait。
5. 基于前文提及的理由,更倾向用 notifyAll(),而不是 notify()。
6.线程sleep结束后不是立刻获取CPU时间片的,要重新等待CPU的时间片分配。
这是关于Java里如何使用wait, notify和notifyAll的所有重点啦。你应该只在你知道自己要做什么的情况下使用这些函数,不然Java里还有很多其它的用来解决同步问题的方案。例如,如果你想使用生产者消费者模型的话,你也可以使用BlockingQueue,它会帮你处理所有的线程安全问题和流程控制。如果你想要某一个线程等待另一个线程做出反馈再继续运行,你也可以使用。如果你只是想保护某一个资源的话,你也可以使用Semaphore。