java多线程:线程协作

目录

线程协作

  • 生产者消费者问题:(线程通信)

    • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
    • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
    • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待直到仓库中再次放入产品为止
  • 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件

    • 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费

    • 对于消费者,在消费之后,要通知知生产者已经结東消费,需要生产新的产品以供消费。

    • 在生产者消费者问题中,仅有 synchronized是不够的

      • synchronized可阻止并发更新同一个共享资源,实现了同步

      • synchronized不能用来实现不同线程之间的消息传递(通信

  • Java提供了几个方法解决线程之间的通信问题

    • void wait() :在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待,直到其他线程通知,与sleep方法不同,会释放锁
    • void wait(long timeout) :指定等待的毫秒数。
    • void notify()唤醒在此对象监视器上等待的单个线程
    • void notifyAll() :唤醒在此对象监视器上等待的所有线程(所有调用wait()方法的线程),优先级别高的线程优先调度。

解决方式一:管程法

  • 并发协作模型“生产者/消费者模式”--->管程法

  • 生产者:负责生产数据的模块(可能是方法、对象、线程、进程)

  • 消费者:负责处理数据的模块(可能是方法、对象、线程、进程)

  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区

  • 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

    //测试:利用缓冲区解决-->管程法
    //生产者、消费者、产品、缓冲区
    public class TestPC {
        public static void main(String[] args) {
            SynContainer container = new SynContainer();
            new Productor(container).start();
            new Consumer(container).start();
        }
    }
    
    //生产者
    class Productor extends Thread {
        SynContainer container;
    
        public Productor(SynContainer container) {
            this.container = container;
        }
        //生产
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                container.push(new Chicken(i));
                //System.out.println("生产了"+i+"只鸡");
            }
        }
    }
    //消费者
    class Consumer extends Thread{
        SynContainer container;
    
        public Consumer(SynContainer container) {
            this.container = container;
        }
        //消费
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                container.pop();
                //System.out.println("消费了-->"+container.pop().id+"只鸡");
            }
        }
    }
    //产品
    class Chicken{
        int id;//产品编号
    
        public Chicken(int id) {
            this.id = id;
        }
    }
    //缓冲区
    class SynContainer{
        //容器大小
        Chicken[] chickens = new Chicken[5];
        //容器计数器
        int count=0;
        //生产者放入产品
        public synchronized void push(Chicken chicken) {
            //如果容器满了,就需要等待消费者消费
            while (count==chickens.length) {
                //通知消费者消费,生产等待
                try {
                    System.out.println("======容器满了,等待消费");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //如果没有满,需要丢入产品
            chickens[count]=chicken;
            count++;
            System.out.println("生产了第"+count+"只鸡");
            //可以通知消费者消费了
            this.notifyAll();
        }
        //消费者消费产品
        public synchronized Chicken pop() {
            //判断能否消费
            while (count == 0) {
                //等待生产者生产,消费者等待
                try {
                    System.out.println("=======鸡没了,消费者等待生产");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //如果可以消费
            System.out.println("消费了-->"+count+"只鸡");
            count--;
            Chicken chicken = chickens[count];
            //吃完了,通知生产者生产
            this.notifyAll();
            return chicken;
        }
    }
    
  • 将println打印放在了pop和push方法体里面,因为可能正要打印的时候,线程切换了,要保证增改操作和打印是一气呵成的(原子操作)

  • 判断count ==10或0的那一句,最好不要用if,应该用while,否则当有多个消费者的时候,会出现脏判断

    在多线程情况下,如果一个线程进入了if语句块中,在if语句块中执行了wait方法,该线程处于等待(wait)状态,如果该线程被唤醒(notify),不管if的条件是否发生了变化,该线程会执行wait之后的代码,并且会跳出if语句块执行if语句块外面的代码

    如果是while,被唤醒了之后,会继续执行wait方法之后的代码,代码块里面的代码执行完了会继续判断条件,如果为true,会继续执行while语句块里面的代码,如果为false,才会执行判断语句块后面的代码

解决方式二:信号灯法

  • 并发协作模型“生产者/消费者模式”--->信号灯法

  • 借助一个标志位来判断生产者和消费者线程的运行

    //信号灯法
    public class TestPC2 {
        public static void main(String[] args) {
            TV tv = new TV();
            new Player(tv).start();
            new Watcher(tv).start();
        }
    }
    //生产者:演员
    class Player extends Thread{
        TV tv;
        public Player(TV tv) {
            this.tv = tv;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                if (i % 2 == 0) {
                    this.tv.play("aaa播放中");
                } else {
                    this.tv.play("广告");
                }
            }
        }
    }
    //消费者:观众
    class Watcher extends Thread{
        TV tv;
        public Watcher(TV tv) {
            this.tv = tv;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                tv.watch();
            }
        }
    }
    //产品:节目
    class TV{
        //演员表演,观众等待T
        //观众观看,演员等待F
        String voice;//表演的节目
        boolean flag = true;
        //表演
        public synchronized void play(String voice) {
            if (!flag) {
                try {
                    System.out.println("========等待观众观看");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("演员表演了"+voice);
            //通知观众观看
            this.notifyAll();//唤醒
            this.voice=voice;
            this.flag=!this.flag;
        }
        //观看
        public synchronized void watch(){
            if (flag) {
                try {
                    System.out.println("============等待演员表演");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("观看了:"+voice);
            //通知演员表演
            this.notifyAll();
            this.flag=!this.flag;
        }
    }
    
  • 感觉与容量为1的管程法有点像

上一篇:java中七大设计原则之依赖倒转原则介绍


下一篇:Android之Xposed