WEB入门浅谈19

多线程

阻塞

阻塞队列,先进先出
阻塞:
这个队列是线程安全的(内部进行了加锁处理)
当队列满的时候,往里插入元素,就会阻塞,直到队列不满时,才会完成插入操作
当队列为空的时候,往出取元素,也会阻塞,直到队列不为空时,才会完成取出操作
阻塞队列可以帮我们完成 生产者消费者模型
在Java标准库中,BlockingQueue 在标准库中就是阻塞队列的一个接口,而LinkedBlockingQueue实现了这个接口,用法如下:

public class Demo01 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> ret = new LinkedBlockingDeque<>();
        ret.put("lit");
        String s = ret.take();
        // take 和 put 方法支持阻塞。
        ret.offer("lit");
        ret.poll();
        ret.element();
        // offer poll element 不支持阻塞
    }
}

阻塞功能的体现:

public class Demo01 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> ret = new LinkedBlockingDeque<>();
        String s = ret.take();
        System.out.println("元素已取出");
    }
}

当运行这段代码时,发现代码不能运行完成,因为ret里没有元素,取不出来,所以处在阻塞状态中

生产者消费者模型如下:

public class Demo02 {
    public static void main(String[] args) {
        BlockingQueue<Integer> ret = new LinkedBlockingDeque<>();//交易场所
        Thread t1 = new Thread(){// 生产者
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    try {
                        ret.put(i);
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t2 = new Thread(){//消费者
            @Override
            public void run() {
                for (int i = 0; i <1000 ; i++) {
                    try {
                        System.out.println(ret.take());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
        t2.start();
    }
}

这就生产速度慢,消费快,每次生产后都立即被消费。

定时器

定时器(Timer)相当于一个闹钟。给定时器设定一个任务,约定这个任务在多少时间之后执行。
Timer提供一个方法 schedule 这个方法指定一个任务交给定时器,在规定的时间去执行这个任务
Timer 中要包含一个Task类,每个 Task 就表示一个具体的任务实例,Task里包含一个时间戳,表示执行任务的具体时间,还包含一个Runnable实例,表示需要执行的任务
Timer里面通过一个带有优先级的阻塞队列,来组织若干个 Task 实例(任务)
Timer还需要一个线程,让这个线程扫描队首元素,看队首元素是否到了执行时间
Timer如下:

public class TaskDemo{
    static class Task implements Comparable<Task>{
        private long time;
        private Runnable command;
        public Task(Runnable command , long time){
            this.command = command;
            this.time = System.currentTimeMillis()+time;
        }
        public void run(){
            command.run();
        }

        @Override
        public int compareTo(Task o) {
            return (int)(this.time - o.time);
        }
    }
    static class Timer{
        private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
        private Object mailBox = new Object();
        public void schedule(Runnable command ,long time){
            Task t = new Task(command,time);
            queue.put(t);
            synchronized (mailBox){
                mailBox.notify();
            }
        }

        public Timer(){
            Thread t1 = new Thread(){
                @Override
                public void run() {
                    while (true){
                        try {
                            Task tmp = queue.take();
                            long time  = System.currentTimeMillis();
                            if (time>=tmp.time){
                                tmp.run();
                            }else {
                                queue.put(tmp);
                                synchronized (mailBox){
                                    mailBox.wait(tmp.time-time);
                                }
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            t1.start();
        }
    }

    public static void main(String[] args) {
        Timer timer = new Timer();
        Runnable com = new Runnable() {
            @Override
            public void run() {
                System.out.println("任务1开始");
            }
        };
        Runnable com1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("任务2开始");
            }
        };
        timer.schedule(com,1000);
        timer.schedule(com1,3000);

    }
}

定时器:
通过Task来描述一个任务
通过schedule来布置任务
带优先级的阻塞队列
线程扫描队首元素
mailBox防止线程忙等

线程池

由于线程的创建与销毁也是不小,所以引入 ”线程池“
线程池把一些线程提前创建好,用的时候从池子里取出一个线程进行使用,使用完毕后不销毁线程,而是把线程放回到线程池里。
在线程池之后,为了解决线程的创建与销毁的开销问题,也引入了一个”协程“
线程的创建与销毁是内核态完成的,而从线程池中取出线程,放回线程是用户态完成的
Java库中提供了线程池:ExecutorService 使用方法如下:

     // 创建一个容量为10个线程的线程池
     ExecutorService pool1 = Executors.newFixedThreadPool(10);
     // 创建一个线程数量动态变化的线程池
     ExecutorService pool2 = Executors.newCachedThreadPool();
     // 插入任务(如果插入任务数大于容量,则会先执行 容量个 的一组任务,然后再执行下一组,直到结束)
     pool1.execute(new Runnable() {
         @Override
         public void run() {
             System.out.println("任务1");
         }
     });
     pool1.submit(new Runnable() {
         @Override
         public void run() {
             System.out.println("任务2");
         }
     });

标准库中还有更精细的 ThreadPool 类,这个类中提供了很多的参数选项来控制线程池的具体行为,ExecutorService 就相当于对其进行了简单的封装
线程池的结构:
描述一个任务,使用 Runnable (不用实现)
组织任务,使用阻塞队列来保存当前所有的任务
有一些线程,用来执行阻塞队列中的任务
还需要一个List把当前的线程保存起来,方便管理

public class MyExecutorService {
    private BlockingQueue<Runnable> queue = new LinkedBlockingDeque<>();
    private static final int MAX_SIZE = 10;//线程池的容量
    static class Worker extends Thread{
        private BlockingQueue<Runnable> queue = null;

        public Worker(BlockingQueue<Runnable> queue){
            this.queue = queue;
        }

        @Override
        public void run() {
            while (true){
                Runnable runnable = null;
                try {
                    runnable = queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                runnable.run();
            }
        }
    }

    private List<Worker> workers = new LinkedList<>();

    public void execute(Runnable command){
        try {
            if (workers.size() < MAX_SIZE){
                Worker worker = new Worker(queue);
                worker.start();
                workers.add(worker);
            }
            queue.put(command);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程池的一些参数
corePoolSize 核心线程数(不包含临时线程)
maximumPoolSize 最大线程数(包含临时线程)
keepAliveTime 临时线程的等待时间(超过这个时间不工作,就销毁)
unit 等待时间的单位
workQueue 任务队列(可以指定长度以及是否优先级)
handler 如果队列满,如何处理新任务(拒绝任务的方式)
ThreadFactory 线程工厂,创建线程的辅助类

补充

生产者消费者模型
生产者生产产品后存放到交易场所内
消费者从交易场所中取出产品
如图:
在这里插入图片描述
交易场所就是一个中间媒介
交易场所就相当于一种数据结构
阻塞队列就是其中的一种,使用阻塞队列就可以很好的协调生产者和消费者之间的工作节奏
如:
一个服务器在同一时刻可能会收到许多请求(不确定时间),而服务器的处理能力是有限的,如果同一时刻收到的请求过多,就有可能导致服务器异常(挂了,可以理解成500状态码)
此时就可以使用生产者消费者模型来进行 削峰 ,削弱请求对服务器的冲击力。
如果服务器直接面对请求,就可能导致服务器异常,而如果在请求与服务器之间添加一个类似阻塞队列的一个数据结构,服务器从阻塞队列中获取请求,请求存入到阻塞队列里,就可以避免服务器异常,此时请求冲击的就是阻塞队列本身,而不是服务器,请求在阻塞队列里进行缓存,不会消耗其它CPU资源


消息队列:
消息队列也是一种阻塞队列,只是提供的功能更加丰富
消息队列里,里面的数据都是带有 类型的 topic ,相同 topic 类型的数据放在不同的队伍中,分别进行排队
一个消息队列,可以同时支持多个业务的多组数据
单独的服务器/服务器集群,通过网络通信的方式,进行 生产销售
支持持久化存储(数据存储在磁盘上)
在进行消费时,支持多种消费模式 如:
支持指定位置消费(不一定只是取出队首元素)
支持镜像模式消费(一个数据可以多次被取出,而不是取一次就删除)


线程池中内定的拒绝策略:
AbortPolicy 直接异常
CallerRunsPolicy 让调用者来决定处理方式
DiscardOldestPolicy 丢弃最早的任务
DiscardPolicy 丢弃最新的任务
如果是一些不能丢弃的数据,就可以先把这些数据记录到一个备用的服务器里(以日志的形式记录)之后在进行处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值