三大线程模型之生产者与消费者(Java版)

一、前言

阅读文章的过程中如果碰到不懂的,可以留言或私信我,我会抽空回答。

编程过程中,我们所有遇到的多线程问题,都可以抽象为三种模型:

  1. 生产者与消费者;
  2. 读者与写者
  3. 哲学家吃饭问题。

能搞清楚这三种模型的实现方式与解决方案,在实际编程中碰到多线程问题处理起来才能得心应手。

二、生产者与消费者

1. 什么是生产者消费者?

生产者与消费者描述的是这样一个多线程问题:有一个或多个生产者生产出某种物品,然后加入到某个容器中,有一个或多个消费者,会不断的从容器中取走物品。当容器满了的时候,生产者会暂时停止生产,当容器为空的时候,消费者会停止消费。

2. 生产者与消费者的线程安全问题

生产者容器里面加东西,消费者容器取出东西,多个线程(生产者消费者)同时对一个对象(容器)进行操作,必然会带来线程安全问题。生产者与消费者模型,最主要的就是如何保证线程安全。

3. 一次简单的尝试

根据上面对生产者和消费者的描述,我们可以用代码给它表达出来:

public class FailProducerConsumer {
    public static void main(String[] args) {
        List<Integer> queue = new ArrayList<>();
        new Thread(new Producer(queue)).start();
        new Thread(new Producer(queue)).start();
        new Thread(new Consumer(queue)).start();
        new Thread(new Consumer(queue)).start();
    }
}

class Producer implements Runnable {
    private final List<Integer> queue;

    Producer(List<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        Integer value = 1;
        while (true) {
            queue.add(value++);
        }
    }
}

class Consumer implements Runnable {
    private final List<Integer> queue;

    Consumer(List<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            if (!queue.isEmpty()) {
                queue.remove(0);
            }
        }
    }
}

上面这段代码存在一个问题:多个Producer和多个Consumer同时对queue进行操作,而queue并不是线程安全的,这会导致结果并不符合预期。

4. 简易版生产者消费者

上面代码的问题是由于queue同时被多个线程操作导致的,解决这个问题最简单的方法就是给queue加锁,让同一时间只能有一个线程操作queue

public class SimpleProducerConsumer {
    private final List<Integer> buffer = new ArrayList<>();

    public static void main(String[] args) {
        BufferOperator bufferOperator = new BufferOperator(new Buffer(new ArrayList<>()));
        new Thread(new Producer(bufferOperator)).start();
        new Thread(new Consumer(bufferOperator)).start();
    }
}

class Producer implements Runnable {
    private final BufferOperator bufferOperator;

    public Producer(BufferOperator bufferOperator) {
        this.bufferOperator = bufferOperator;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            try {
                bufferOperator.offer(i++);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Consumer implements Runnable {
    private final BufferOperator bufferOperator;

    public Consumer(BufferOperator bufferOperator) {
        this.bufferOperator = bufferOperator;
    }

    @Override
    public void run() {
        while (true) {
            try {
                bufferOperator.poll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Buffer {
    private final List<Integer> buffer;

    public Buffer(List<Integer> buffer) {
        this.buffer = buffer;
    }

    public void add(Integer object) {
        this.buffer.add(object);
    }

    public Integer removeFirst() {
        return this.buffer.remove(0);
    }

    public boolean isEmpty() {
        return buffer.isEmpty();
    }
}

class BufferOperator {
    private final Buffer buffer;

    public BufferOperator(Buffer buffer) {
        this.buffer = buffer;
    }

    public void offer(Integer object) throws InterruptedException {
        synchronized (buffer) {
            System.out.println("add object " + object);
            buffer.add(object);
        }
        Thread.sleep(500);
    }

    public void poll() throws InterruptedException {
        synchronized (buffer) {
            if (!buffer.isEmpty()) {
                System.out.println("poll  + " + buffer.removeFirst());
            } else {
                System.out.println("Queue is empty!");
            }
        }
        Thread.sleep(1000);
    }
}

小知识点:这段代码对Buffer的操作放到BufferOperator中,将操作Buffer的部分交给BufferOperator,是为了符合SRP原则。

5. 使用BlockingQueue实现生产者消费者

Java其实已经给我们提供了一种线程安全的队列:BlockingQueue,常用的有ArrayBlockingQueueLinkedBlockingQueue(有界队列和无界队列)。如果我们使用BlockingQueue来实现生产者与消费者,则无需担心线程安全问题,会方便很多。代码如下:

public class BQProducerConsumer {

    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingDeque<>();
        new Thread(new Consumer(queue)).start();
        new Thread(new Producer(queue)).start();
    }
}

class Consumer implements Runnable {
    private final BlockingQueue<Integer> queue;

    Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            System.out.println("offer " + i++);
            queue.offer(i);
        }
    }
}

class Producer implements Runnable {
    private final BlockingQueue<Integer> queue;

    Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("poll " + queue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
6. 使用线程池实现生产者消费者
public class ThreadPoolProducerConsumer {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
        // Executors vs ThreadPoolExecutors
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.submit(new Producer(queue));
        executorService.submit(new Consumer(queue));
    }
}

class Consumer implements Runnable {
    private final BlockingQueue<Integer> queue;

    Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            System.out.println("offer " + i);
            queue.offer(i++);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer implements Runnable {
    private final BlockingQueue<Integer> queue;

    Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("poll " + queue.take());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值