一、生产与消费模型 生产和消费模型: 通过不同的线程操作,来控制同一个资源,这种现象就属于生产消费模型 生产:多个线程同时给水池中注水 消费:多个线程同时从水池中抽水
1.简单的实现生产消费模型 创建公共资源类
package com.wangxing.test1; /** * 被多个线程操作的共享数据的共享资源类 * @author Administrator * */ public class Resource { // 注水的方法 public void add() { } // 抽水的方法 public void delete() { } }生产共享资源的目标类
package com.wangxing.test1; /** * 生产共享资源的目标类 * @author Administrator * */ public class ShengChan implements Runnable{ //定义共享资源对象 private Resource resource; //通过构造方法传入共享资源对象 public ShengChan(Resource resource) { this.resource=resource; } @Override public void run() { //访问共享资源的生产方法 resource.add(); } }消费共享资源的目标类
package com.wangxing.test1; /** * 消费共享资源的目标类 * @author Administrator * */ public class XiaoFei implements Runnable{ //定义共享资源对象 private Resource resource; //通过构造方法传入共享资源对象 public XiaoFei(Resource resource) { this.resource=resource; } @Override public void run() { //访问共享资源的消费方法 resource.delete(); } }测试主类
package com.wangxing.test1; public class TestMain { public static void main(String[] args) { //创建共享资源类对象 Resource resource=new Resource(); //创建生产共享资源的目标类对象 ShengChan sc=new ShengChan(resource); //创建消费共享资源的目标类对象 XiaoFei xf=new XiaoFei(resource); //创建生产者线程对象 Thread scthread=new Thread(sc); //创建消费者线程对象 Thread xfthread=new Thread(xf); //启动生产和消费线程 scthread.start(); xfthread.start(); } }修改Resource为add()/delete()添加具体实现动作。
package com.wangxing.test1; /** * 被多个线程操作的共享数据的共享资源类 * @author Administrator * */ public class Resource { //保存共享资源的数组【水池】 private Object objs[] = new Object[1]; //记录生产和消费的次数 private int num = 1; // 注水的方法 public void add() { objs[0] = "水" + num; System.out.println(Thread.currentThread().getName() + "正要注进入的水是:" + objs[0]); num++; } // 抽水的方法 public void delete() { System.out.println(Thread.currentThread().getName() + "抽出的水是:" + objs[0]); objs[0] = null; } }修改生产者的目标类为注水方法添加死循环,以达到持续注水的目标。
package com.wangxing.test1; /** * 生产共享资源的目标类 * @author Administrator * */ public class ShengChan implements Runnable{ //定义共享资源对象 private Resource resource; //通过构造方法传入共享资源对象 public ShengChan(Resource resource) { this.resource=resource; } @Override public void run() { //持续注水 while(true){ //访问共享资源的生产方法 resource.add(); } } }修改消费者的目标类为抽水方法添加死循环,以达到持续抽水的目标。
package com.wangxing.test1; /** * 消费共享资源的目标类 * @author Administrator * */ public class XiaoFei implements Runnable{ //定义共享资源对象 private Resource resource; //通过构造方法传入共享资源对象 public XiaoFei(Resource resource) { this.resource=resource; } @Override public void run() { //持续抽水 while(true){ //访问共享资源的消费方法 resource.delete(); } } }问题:有时会出现生产者注水为null 的情况,有时会出现消费者抽水为null的情况。是因为当前线程正在访问的共享资源的时候,其他的线程页可以访问共享资源所产生的。所以线程操作共享数据时,需要进行线程同步。 解决方法:修改Resource为注水和抽水方法添加同步代码快保证注水的时候不能抽水,或者抽水的时候不能给当前这个空间注水。
package com.wangxing.test1; /** * 被多个线程操作的共享数据的共享资源类 * @author Administrator * */ public class Resource { //保存共享资源的数组【水池】 private Object objs[] = new Object[1]; //记录生产和消费的次数 private int num = 1; //创建一个同步对象 private static final Object loc = new Object(); // 注水的方法 public void add() { synchronized (loc) { objs[0] = "水" + num; System.out.println(Thread.currentThread().getName() + "正要注进入的水是:" + objs[0]); num++; } } // 抽水的方法 public void delete() { synchronized (loc) { System.out.println(Thread.currentThread().getName() + "抽出的水是:" + objs[0]); objs[0] = null; } } }问题:会出现多次注水没有抽水,或者多次抽水,没有注水的问题 解决方法:先判断是否满足抽水或者注水的条件 如果不满足注水的时候,但是当前正好CPU在注水的线程上,这时就必须让这个注水的线程等待,等到可以注水的时候将本次注水的动作做完。 如果不满足抽水的时候,但是当前正好CPU在抽水的线程上,必须让抽水的线程等待,等到数组有水的时候将本次的抽水的动作做完。 需要使用Java中线程的等待和唤醒机制(线程间的通信)
注水线程注水结束之后,应该告诉抽水线程可以抽水。同样道理,抽水线程抽完水之后,应该告诉注水线程可以注水了。
唤醒:当某个一方操作完成之后,需要将处于另外一方操作的等待的线程等待的状态恢复到可以操作的状态(把一方通知另外一方的这个操作称为线程的唤醒)。 等待和唤醒(线程通信)必须位于同步中。因为等待和唤醒必须使用当前的锁才完成。
修改Resource为注水和抽水方法添加线程等待和唤醒操作
package com.wangxing.test1; /** * 被多个线程操作的共享数据的共享资源类 * @author Administrator * */ public class Resource { //保存共享资源的数组【水池】 private Object objs[] = new Object[1]; //记录生产和消费的次数 private int num = 1; //创建一个同步对象 private static final Object loc = new Object(); // 注水的方法 public void add() throws InterruptedException { synchronized (loc) { //注水时判断数组是否有水 //有水,就无需注水,如果此时正好切换到注水线程, //那么注水线程就应该等待 if(objs[0]!=null) { //注水线程等待 loc.wait(); } objs[0] = "水" + num; System.out.println(Thread.currentThread().getName() + "正要注进入的水是:" + objs[0]); num++; //唤醒抽水线程运行 loc.notify(); } } // 抽水的方法 public void delete()throws InterruptedException{ synchronized (loc) { //抽水时判断数组是否有水 //没水,就无需抽水,如果此时正好切换到抽水线程, //那么抽水线程就应该等待 if(objs[0]==null) { //抽水线程等待 loc.wait(); } System.out.println(Thread.currentThread().getName() + "抽出的水是:" + objs[0]); objs[0] = null; //唤醒注水线程运行 loc.notify(); } } }上面的程序处理好了单线程的注水和抽水动作。
2.多线程的注水和抽水动作 方法:修改主类多创建几个注水和抽水线程对象,并启动运行 问题:将单注水和单抽水修改为两个注水和两个抽水,结果程序中又出现了多次注水,或者多次抽水的现象。 原因:是因为在唤醒的时候,抽水的线程将另外一个抽水的线程唤醒了。或者注水的线程将另外一个注水的线程唤醒了。只要自己同伴线程将自己唤醒之后,这时被唤醒的线程就可以继续操作。导致出现了多次注水,或者多次抽水的现象。 解决:将判断有没有水的if修改为while即可。唤醒之后可以继续判断。
package com.wangxing.test1; /** * 被多个线程操作的共享数据的共享资源类 * @author Administrator * */ public class Resource { //保存共享资源的数组【水池】 private Object objs[] = new Object[1]; //记录生产和消费的次数 private int num = 1; //创建一个同步对象 private static final Object loc = new Object(); // 注水的方法 public void add() throws InterruptedException { synchronized (loc) { //注水时判断数组是否有水 //有水,就无需注水,如果此时正好切换到注水线程, //那么注水线程就应该等待 while(objs[0]!=null) { //注水线程等待 loc.wait(); } objs[0] = "水" + num; System.out.println(Thread.currentThread().getName() + "正要注进入的水是:" + objs[0]); num++; //唤醒抽水线程运行 loc.notify(); } } // 抽水的方法 public void delete()throws InterruptedException{ synchronized (loc) { //抽水时判断数组是否有水 //没水,就无需抽水,如果此时正好切换到抽水线程, //那么抽水线程就应该等待 while(objs[0]==null) { //抽水线程等待 loc.wait(); } System.out.println(Thread.currentThread().getName() + "抽出的水是:" + objs[0]); objs[0] = null; //唤醒注水线程运行 loc.notify(); } } }新的问题: 修改为while之后,死锁(所有的线程都处于等待状态了。外面没有可以执行的线程了) 解决方案: 使用notifyAll唤醒所有线程。每次在唤醒的时候都是唤醒所有线程,即使唤醒了自己的同伴,也无所谓,因为还要继续判断,这样一定还会等待,但是唤醒唤醒中一定有另外一方的线程,它们肯定不会等待。它们不等待,就会去操作,它们操作完成也唤醒所有。 缺点: 多生产多消费的程序中,为了保证不出现全部线程被wait的情况,只能在唤醒的时候使用notifyAll将所有处于等待的线程唤醒。这样每次都可以保证一定会有存活的线程。但是这种唤醒效率太低了,经常会发生生产方唤醒自己的同伴线程,或者是消费方唤醒自己的同伴线程。
package com.wangxing.test1; /** * 被多个线程操作的共享数据的共享资源类 * @author Administrator * */ public class Resource { //保存共享资源的数组【水池】 private Object objs[] = new Object[1]; //记录生产和消费的次数 private int num = 1; //创建一个同步对象 private static final Object loc = new Object(); // 注水的方法 public void add() throws InterruptedException { synchronized (loc) { //注水时判断数组是否有水 //有水,就无需注水,如果此时正好切换到注水线程, //那么注水线程就应该等待 while(objs[0]!=null) { //注水线程等待 loc.wait(); } objs[0] = "水" + num; System.out.println(Thread.currentThread().getName() + "正要注进入的水是:" + objs[0]); num++; //唤醒抽水线程运行 loc.notifyAll(); } } // 抽水的方法 public void delete()throws InterruptedException{ synchronized (loc) { //抽水时判断数组是否有水 //没水,就无需抽水,如果此时正好切换到抽水线程, //那么抽水线程就应该等待 while(objs[0]==null) { //抽水线程等待 loc.wait(); } System.out.println(Thread.currentThread().getName() + "抽出的水是:" + objs[0]); objs[0] = null; //唤醒注水线程运行 loc.notifyAll(); } } }Condition接口:代替等待和唤醒机制 Condition接口可以创建出不同的等待和唤醒的对象,用在不同的场景下: 可以创建一个Condition对象,专门负责生产。 可以创建一个Condition对象,专门负 责消费。 可以通过负责生产的Condition对象专门监视负责生产的线程。通过负责消费的Condition监视消费的线程。等待和唤醒的时候,可以使用各自的Condition对象。 注意:如果要想使用Condition接口,同步必须使用Lock接口。 如果程序中同步使用的同步代码块,等待和唤醒只能使用Object中的wait、notify、notifyAll方法。 只有同步使用的Lock接口,等待和唤醒才能使用Condition接口。
package com.wangxing.test1; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 被多个线程操作的共享数据的共享资源类 * @author Administrator * */ public class Resource { //保存共享资源的数组【水池】 private Object objs[] = new Object[1]; //记录生产和消费的次数 private int num = 1; // 创建Lock接口,作为同步的锁 private Lock lock = new ReentrantLock(); // 负责监视注水的线程 private Condition scCondition = lock.newCondition(); // 负责监视抽水的线程 private Condition xfCondition = lock.newCondition(); // 注水的方法 public void add() { try { //线程同步,获取锁 lock.lock(); //注水时判断数组是否有水 //有水,就无需注水,如果此时正好切换到注水线程, //那么注水线程就应该等待 while(objs[0]!=null) { //注水线程等待 scCondition.await(); } objs[0] = "水" + num; System.out.println(Thread.currentThread().getName() + "正要注进入的水是:" + objs[0]); num++; //唤醒抽水线程运行 xfCondition.signal(); }catch(Exception e) { e.printStackTrace(); }finally { //手动释放锁 lock.unlock(); } } // 抽水的方法 public void delete(){ try { //线程同步,获取锁 lock.lock(); //抽水时判断数组是否有水 //没水,就无需抽水,如果此时正好切换到抽水线程, //那么抽水线程就应该等待 while(objs[0]==null) { //抽水线程等待 xfCondition.await(); } System.out.println(Thread.currentThread().getName() + "抽出的水是:" + objs[0]); objs[0] = null; //唤醒注水线程运行 scCondition.signal(); }catch(Exception e) { e.printStackTrace(); }finally { //手动释放锁 lock.unlock(); } } }2.为什么使用Lock接口替换同步代码块 使用同步代码【synchronized】实现线程同步的缺点是通过Object类提供的notifyAll这个方法可能会唤醒自己的同伴线程,那么程序就会多执行一次是否注水/抽水的判断过程,这样程序的执行效率就会降低。为了提高程序的运行效率,在唤醒等待的线程的时候,只唤醒注水线程/抽水线程,而不会唤醒自己的同伴线程,这时我们就需要使用Condition 接口提供的等待和唤醒方法【await(),signal(),signalAll()】,因为他可以只唤醒对方线程,而不会唤醒同伴线程。Condition 接口在使用的时候是需要Lock接口对象的newCondition 方法才能创建出Condition 接口对象。所以我们在此处就使用Lock接口对象实现线程同步,来代替同步代码【synchronized】,实现线程同步。
同步代码【synchronized】-----Object类提供的wait、notify、notifyAll方法 Lock接口对象实现线程同步----Condition 接口提供的await(),signal(),signalAll()方法
3.sleep 与wait的区别 4.notify 与notifyAll的区别
