notify()-notifyAll()-wait()-join()-yield()-sleep()
一、wait(),notify(),notifyAll()的理解与使用
这三个方法由于需要控制对对象的控制权(monitor),所以属于Object而不是属于线程。
wait(),会把持有该对象线程的对象控制权交出去,然后处于等待状态。
notify(),会通知某个正在等待这个对象的控制权的线程可以继续运行。
nofifyAll(),会通知所有等待这个对象控制权的线程继续运行,如果有多个正在等待该对象控制权时,具体唤醒哪个线程,就由操作系统进行调度。
下面我们先看一个例子:
package com.youyou.ch1.wn; /** *类说明:快递实体类 */ public class Express { public final static String CITY = "ShangHai"; private int km;/*快递运输里程数*/ private String site;/*快递到达地点*/ public Express() { } public Express(int km, String site) { this.km = km; this.site = site; } /* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/ public synchronized void changeKm(){ this.km = 101; notify(); //其他的业务代码 } /* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/ public synchronized void changeSite(){ this.site = "BeiJing"; notifyAll(); } public synchronized void waitKm(){ while(this.km<=100) { try { wait(); System.out.println("check km thread["+Thread.currentThread().getId() +"] is be notifed."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("the km is"+this.km+",I will change db."); } public synchronized void waitSite(){ while(CITY.equals(this.site)) { try { wait(); System.out.println("check site thread["+Thread.currentThread().getId() +"] is be notifed."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("the site is"+this.site+",I will call user."); } }
package com.youyou.ch1.wn; /** *类说明:测试wait/notify/notifyAll */ public class TestWN { private static Express express = new Express(0,Express.CITY); /*检查里程数变化的线程,不满足条件,线程一直等待*/ private static class CheckKm extends Thread{ @Override public void run() { express.waitKm(); } } /*检查地点变化的线程,不满足条件,线程一直等待*/ private static class CheckSite extends Thread{ @Override public void run() { express.waitSite(); } } public static void main(String[] args) throws InterruptedException { for(int i=0;i<3;i++){//三个线程 new CheckSite().start(); } for(int i=0;i<3;i++){//里程数的变化 new CheckKm().start(); } Thread.sleep(5000); express.changeKm();//快递地点变化 } }
结果总结:
1.在测试的时候,我们可以改变notify和notifyAll,看看结果是什么。结果会是完全不同的;
2.通过改变为notify的时候,我们发现的结果,我们知道,等待的线程,会被放到 队列里面,被唤醒的时候,就不太明确是哪个了?(由于是队列,那应该就是第一个)
我们来看另外的一个例子:
注意:
1.生产者,消费者必须要对同一份资源进行操作。
2.无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor)
生产者:
package com.currentPro.waitAndnotify; import java.util.ArrayList; import java.util.List; public class Producer implements Runnable{ private List<Integer> taskQueue = new ArrayList<Integer>(); //生产的量 private final int MAX_CAPACITY; public Producer(List<Integer> sharedQueue,int size){ this.taskQueue = sharedQueue; this.MAX_CAPACITY = size; } @Override public void run() { int counter = 1; while (true) { try { synchronized (taskQueue) { while (taskQueue.size() == MAX_CAPACITY) { System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size()); taskQueue.wait(); } Thread.sleep(1000); taskQueue.add(counter); System.out.println("Produced: " + counter); counter++; //唤醒正在等待的消费者,但是消费者是不是能获取到资源,由系统调度。 taskQueue.notifyAll(); } } catch (InterruptedException ex) { ex.printStackTrace(); } } } }
消费者:
package com.currentPro.waitAndnotify; import java.util.List; public class Consumer implements Runnable{ private final List<Integer> taskQueue ; public Consumer(List<Integer> sharedQueue){ this.taskQueue = sharedQueue; } @Override public void run() { while (true) { try { synchronized (taskQueue) { while (taskQueue.isEmpty()) { System.out.println("Queue is empty " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size()); taskQueue.wait(); } Thread.sleep(1000); int i = (Integer) taskQueue.remove(0); System.out.println("Consumed: " + i); taskQueue.notifyAll(); } } catch (InterruptedException ex) { ex.printStackTrace(); } } } }
测试代码:
package com.currentPro.waitAndnotify; import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) throws InterruptedException { //共享资源 List<Integer> taskQueue = new ArrayList<Integer>(); int MAX_CAPACITY = 5; //创建生产者线程 Thread producer = new Thread(new Producer(taskQueue,MAX_CAPACITY),"producer"); //创建消费者线程 Thread consumer = new Thread(new Consumer(taskQueue),"consumer"); consumer.start(); Thread.sleep(2000); producer.start(); } }
二、join()-方法详解
现在我们还是认为,多个相同的线程是抢占式的,但是,我们有没有方法,让线程A,一定在线程B 之前先执行呢?
这里的话,我们可以暂时知道有两个方法,一个是 join() , 另外一个是 CountDownLatch。
下面我们先来讲述一下 join 方法。
下面我们来看一个简单的例子:
package com.youyou.ch1.demo1; public class UseJoin { static class JumpQueue implements Runnable{ private Thread thread; public JumpQueue(Thread thread){ this.thread = thread; } public void run(){ try { System.out.println(thread.getName()+" will be join before " +Thread.currentThread().getName()); thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" terminted."); } } public static void main(String[] args) throws InterruptedException { Thread previous = Thread.currentThread();//现在是主线程 for (int i = 0; i < 10; i++) { Thread thread = new Thread(new JumpQueue(previous),String.valueOf(i)); System.out.println(previous.getName()+" jump a queue the thread:" +thread.getName()); thread.start(); previous = thread; } Thread.sleep(2000);//让主线程休眠2秒 System.out.println(Thread.currentThread().getName() + " terminate."); } }
下面我们在来一个简单的总结:
面试点
线程在执行yield()以后,持有的锁是不释放的
sleep()方法被调用以后,持有的锁是不释放的
调动方法之前,必须要持有锁。调用了wait()方法以后,锁就会被释放,当wait方法返回的时候,线程会重新持有锁
调动方法之前,必须要持有锁,调用notify()方法本身不会释放锁的