多线程(进阶)

阻塞队列(生产者消费者模式)

生产者一批线程(负责向共享存储区域存放数据),消费者一批线程(负责从共享存储区域取出数据),当共享存储区域数据没有了,消费者需要等一会儿,直到生产者将数据放入共享存储区。当共享存储区域被填充满了,生产者需要等一会儿,等待消费者将数据取出,让共享存储区域有位置可以放!

下面请看代码


// 隐式加锁 使用 synchronized (智慧的锁,根据不同的情况产生不同的锁策略)!
public class BlockQueue {
    private long[] array;
    private int frontIndex;
    private int rearIndex;
    private int size;

    public BlockQueue(int capacity) {
        array = new long[capacity];
        frontIndex = 0;
        rearIndex = 0;
        size = 0;
    }

    public synchronized void put(long e) throws InterruptedException {
        // 是不是满了
        while (size == array.length) {
            wait();// 作为 P,在等 C
        }

        // 队列不是满的
        array[rearIndex] = e;
        rearIndex++;
        if (rearIndex == array.length) {
            rearIndex = 0;
        }
        size++;

        notifyAll();   //notifyAll() 唤醒等待集合中的所有线程(不区分是消费者线程还是生产者线程)!
    }

    public synchronized long take() throws InterruptedException {
        while (size == 0) {
            wait(); // 作为 C,在等 P
        }

        // 队列一定不是空的
        long e = array[frontIndex];
        frontIndex++;
        if (frontIndex == array.length) {
            frontIndex = 0;
        }
        size--;

        notifyAll();   //notifyAll() 唤醒等待集合中的所有线程(不区分是消费者线程还是生产者线程)!
        
        return e;
    }
}

// 显式加锁
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BlockQueue<E> {
    private Object[] array;
    private int size;
    private int from_index;
    private int rear_index;
    private Lock lock ;
    private Condition notEmpty; // 生产者等待集(队列不为空)
    private Condition notFull; // 消费者等待集(队列不为满)
    public BlockQueue(int capacity){
        array = new Object[capacity];
        size = 0;
        from_index = 0;
        rear_index = 0;
        lock = new ReentrantLock(true); //公平锁
        notEmpty = lock.newCondition();
        notFull = lock.newCondition();
    }

    // 当队列为空,消费者等待,生产者工作。队列满了,生产者等待,消费者工作!
    public void push(E element) throws InterruptedException {
        try{
            lock.lock(); // 显示加锁!
            while (size == array.length) notFull.await();
            array[rear_index] = element;
            rear_index++;
            if(rear_index == array.length){
                rear_index = 0;
            }
            size++;
            notEmpty.signal();
        }finally {
            lock.unlock();
        }
    }

    public E take() throws InterruptedException {
        E ret = null;
        try {
            lock.lock();
            while (size == 0) notEmpty.await();
            ret = (E) array[from_index];
            from_index++;
            if (from_index == array.length) from_index = 0;
            size--;
            notFull.signal();
        } finally {
            lock.unlock(); // 确保锁一定会被释放!
            return ret;
        }
    }
}

基于阻塞队列的应用

定时器

能力有限,献丑了!

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;

/**
 *  实现一个定时器!
 */
public class MyTimer {
    // 默认小顶堆!
    private BlockingQueue<Task> blockQueue = new PriorityBlockingQueue<>(); // 优先阻塞队列!
    private Object newTaskCom = new Object();


    static class Task implements Runnable,Comparable<Task> {
        private boolean isPermanent; // 是否为循环执行任务!
        private long runAt; // 启动任务的时间!
        private long delay; // 启动任务的时间间隔!
        private Runnable work; // 任务!

        public Task(long period, Runnable work) {
            this.delay = period;
            this.work = work;
            long curTime = System.currentTimeMillis();
            this.runAt = curTime + period;
        }

        public Task(boolean isPermanent, long period, Runnable work) {
            this.isPermanent = isPermanent;
            this.delay = period;
            this.work = work;
            long curTime = System.currentTimeMillis();
            this.runAt = curTime + period;
        }

        @Override
        public void run() {
            work.run();
        }

        @Override
        public int compareTo(Task o) {
            long sub = this.runAt - o.runAt;
            return sub == 0 ? 0 : sub > 0 ? 1 : -1;
        }

        public long getRunAt() {
            return runAt;
        }

        public void setRunAt(long runAt) {
            this.runAt = runAt;
        }

        public boolean isPermanent() {
            return isPermanent;
        }

        public void setPermanent(boolean permanent) {
            isPermanent = permanent;
        }

        public long getDelay() {
            return delay;
        }

        public void setDelay(long delay) {
            this.delay = delay;
        }
    }

    // 消费者
    static class TakeRun extends Thread{
        private BlockingQueue<Task> blockingQueue;
        private Object newTaskCom;

        public TakeRun(BlockingQueue<Task> blockingQueue,Object newTaskCom) {
            this.blockingQueue = blockingQueue;
            this.newTaskCom = newTaskCom;
        }
        // 从阻塞队列中取出元素,并执行!
        @Override
        public void run() {
            try {

                while(true){
                   Task task = blockingQueue.take();
                   long cur_time = System.currentTimeMillis();
                    if(cur_time < task.getRunAt()){ // 未到执行时间!
                        long gap = task.getRunAt() - cur_time;
                        synchronized (newTaskCom){ // 未到执行时间!
                            newTaskCom.wait(gap); // 当有新的元素加入优先队列会提前醒过来!
                        }
                        long cur = System.currentTimeMillis();
                        if(cur >= task.getRunAt()){ // 醒过来的时候,没有比他更先执行的任务或者没来新的任务!
                            task.run();
                            // 如果是周期执行!
                            if(task.isPermanent()){
                                task.setRunAt(System.currentTimeMillis() + task.getDelay());
                                blockingQueue.put(task);
                            }

                        }else{
                            // 说明有优先级比取出的更高的!
                            blockingQueue.put(task);
                        }
                    }else{ //已经到了执行时间!
                        task.run();
                        if(task.isPermanent()){
                            task.setRunAt(System.currentTimeMillis() + task.getDelay());
                            blockingQueue.put(task);
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 开启一个定时器!
    public MyTimer() {
        TakeRun consumer = new TakeRun(blockQueue,newTaskCom);
        consumer.start();
    }

    // 向定时器中放任务!
    public void put(Task task){
        try {
            blockQueue.put(task);
            synchronized (newTaskCom){
                newTaskCom.notify();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        MyTimer timer = new MyTimer();
        Task task = new Task(true,1000, new Runnable() {
            @Override
            public void run() {
                System.out.println("1s 之后!");
            }
        });
        timer.put(task);
    }
}

线程池

话不多说,上代码!


import java.util.concurrent.BlockingQueue;

// 一个正式员工线程要完成的工作
public class CoreJob implements Runnable {
    // 需要阻塞队列
    private final BlockingQueue<Runnable> workQueue;
    private Runnable firstCommand;

    CoreJob(BlockingQueue<Runnable> workQueue, Runnable firstCommand) {
        this.workQueue = workQueue;
        this.firstCommand = firstCommand;
    }

    @Override
    public void run() {
        try {
            firstCommand.run();     // 优先先把刚提交的任务先做掉了
            firstCommand = null;    // 这里设置 null 的意思是,不影响 firstCommand 对象被 GC 时的回收

            while (!Thread.interrupted()) {
                Runnable command = workQueue.take();
                command.run();
            }
        } catch (InterruptedException ignored) {}
    }
}


import java.util.concurrent.*;

// 线程池类
public class MyThreadPoolExecutor implements Executor {
    // 创建线程的工厂对象
    private final ThreadFactory threadFactory;

    // 临时工摸鱼的时间上限
    private final long keepAliveTime;
    private final TimeUnit unit;

    // 当前正式员工的数量
    private int currentCoreSize;

    // 正式员工的数量上限
    private final int corePoolSize;

    // 当前临时员工的数量
    private int currentTemporarySize;

    // 临时员工的数量上限
    private final int temporaryPoolSize;

    // 传递任务的阻塞队列
    private final BlockingQueue<Runnable> workQueue;

    public MyThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                long keepAliveTime,
                                TimeUnit unit,
                                BlockingQueue<Runnable> workQueue,
                                ThreadFactory threadFactory,
                                RejectedExecutionHandler handler) {
        this.corePoolSize = corePoolSize;
        this.temporaryPoolSize = maximumPoolSize - corePoolSize;
        this.workQueue = workQueue;
        this.threadFactory = threadFactory;
        this.keepAliveTime = keepAliveTime;
        this.unit = unit;
    }

    // 向线程池中提交任务
    // 目前这个方法不是线程安全版本的
    @Override
    public void execute(Runnable command) {
        // 1. 如果正式员工的数量还低于正式员工的数量上限,则优先创建正式员工处理任务
        // 1.1 需要管理,当前正式员工有多少,正式员工的数量上限有多少?
        if (currentCoreSize < corePoolSize) {
            // 优先创建正式员工进行处理
            // 创建一个线程,这个线程中的任务就是不断地取任务-做任务,但是不需要考虑退出的问题
            CoreJob job = new CoreJob(workQueue, command);
//            Thread thread = new Thread(job);    // 不使用工厂创建的线程
            Thread thread = threadFactory.newThread(job);   // thread 代表的就是正式员工
            String name = String.format("正式员工-%d", currentCoreSize);
            thread.setName(name);

            thread.start();

            // 只是两种不同的策略,没有谁是正确的说法
            // 1. 把 command 放到队列中;command 的执行次序是在队列已有的任务之后
            // 2. 创建正式员工的时候,就把 command 提交给正式员工,让 command 优先执行
            // 我们这里采用第二种方案,主要原因就是 java 官方的就是使用的第二种策略

            currentCoreSize++;
            return;
        }

        // 走到这里,说明正式员工的数量 == 正式员工的上限了
        // 2. 优先把任务放入队列中,如果放入成功,execute 执行结束,否则还需要继续
        // 2.1 需要一个阻塞队列
//        workQueue.put(command); // 带阻塞的放入,是否满足这里的需求?
                                // 我们这里希望的是立即得到结果
        boolean success = workQueue.offer(command);
        if (success == true) {
            // 说明放入队列成功
            return;
        }

        // 队列也已经放满了
        // 3. 继续判断,临时工的数量有没有到上限,如果没有到达,创建新的临时工来处理
        if (currentTemporarySize < temporaryPoolSize) {
            // 创建临时工进行处理
            TemporaryJob job = new TemporaryJob(keepAliveTime, unit, workQueue, command);
//            Thread thread = new Thread(job);    // 不使用工厂创建的线程
            Thread thread = threadFactory.newThread(job);   // thread 代表的就是临时员工
            String name = String.format("临时员工-%d", currentTemporarySize);
            thread.setName(name);

            thread.start();

            currentTemporarySize++;
            return;
        }

        // 4. 执行拒绝策略
        // 为了实现方便,暂时不考虑其他策略
        throw new RejectedExecutionException();
    }
}


import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

// 一个临时员工线程要完成的工作
public class TemporaryJob implements Runnable {
    // 需要阻塞队列
    private final BlockingQueue<Runnable> workQueue;
    private final long keepAliveTime;
    private final TimeUnit unit;
    private Runnable firstCommand;

    TemporaryJob(long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, Runnable firstCommand) {
        this.keepAliveTime = keepAliveTime;
        this.unit = unit;
        this.workQueue = workQueue;
        this.firstCommand = firstCommand;
    }

    @Override
    public void run() {
        try {
            firstCommand.run();     // 优先先把刚提交的任务先做掉了
            firstCommand = null;    // 这里设置 null 的意思是,不影响 firstCommand 对象被 GC 时的回收

            // 一旦超过一定时间没有任务,临时工是需要退出的
            // 1. keepAliveTime + unit 记录起来
            // 2. 怎么就知道超过多久没有任务了?如果一定时间内都无法从队列中取出来任务,则认为摸鱼时间够了
            while (!Thread.interrupted()) {
//                Runnable command = workQueue.take();
                Runnable command = workQueue.poll(keepAliveTime, unit);
                if (command == null) {
                    // 说明,没有取到任务
                    // 说明超时时间已到
                    // 说明该线程已经 keepAliveTime + unit 时间没有工作了
                    // 所以,可以退出了
                    break;
                }
                command.run();
            }
        } catch (InterruptedException ignored) {}
    }
}

import java.util.concurrent.*;

public class Main {
    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(15);
            } catch (InterruptedException ignored) {}
        }
    }

    static class MyThreadFactory implements ThreadFactory {
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r);
        }
    }

    public static void main(String[] args) {
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(5);
        // 同时最多有 15 个任务
        // 3 个正式的
        // 5 个队列中
        // 7 个临时的
        // 提交第 16 个任务时就会出现拒绝服务
        MyThreadPoolExecutor executor = new MyThreadPoolExecutor(
                3, 10, 10, TimeUnit.SECONDS,
                workQueue,
                new MyThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        // [0, 1, 2] 交给正式员工在处理
        // [3, 4, 5, 6, 7] 暂时放在队列中
        // [8, 9, 10, 11, 12, 13, 14] 交给临时工处理
        // 过了 15s 之后,第一批任务执行结束
        // [0, 1, 2]、[8, 9, 10, 11, 12, 13, 14] 执行结束
        // 剩下的 [3, 4, 5, 6, 7] 任务具体怎么分配不确定,大概率是交给正式员工执行
        // 就算极端情况下,5 个全部给了临时工
        // 也至少还有 2 个临时工没有工作
        // 再过 10s,至少 2 个,最多 5 个临时工要被解雇
        for (int i = 0; i < 15; i++) {
            System.out.println("提交任务: " + i);
            Task task = new Task();
            executor.execute(task);
        }
    }
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值