多线程案例

单例模式

首先我们需要知道什么是设计模式
设计模式就像“棋谱”,针对常见的问题场景,大佬们总结的固定套路。
那么单例模式是什么?
单例模式就是限制类只有唯一的实例。
下面我们以洗碗这个情景来讲述饿汉模式和懒汉模式

饿汉模式

饿汉模式就相当于洗碗池里有一堆碗,而你此时要吃饭,你就一股脑的把所有碗都洗了,用的时候全部洗完。
饿汉模式:使用static在类加载阶段创建实例

 private  Singleton() {

    }
    private static Singleton instance=new Singleton();

    public static Singleton getInstance() {
        return instance;
    }

当多个线程调用getInstance时,是多个线程读,不涉及修改线程安全。

懒汉模式

懒汉模式就相当于你要吃饭,洗碗时只洗一个吃饭的碗,用的时候再洗,用多少洗多少。
懒汉模式:通过getInstance方法获取实例。首次调用该方法,才真正创建实例。(延时加载)

    private  Singleton() {}
    private static Singleton instance=null;
    public static Singleton getInstance() {
        if(instance==null) {
            instance=new Singleton();
        }
        return instance;
    }

如果实例已经创建完,再调用getInstance,此时不涉及修改,线程安全。
如果实例没有创建,此时可能涉及到修改,如果多个线程同时修改就存在线程安全问题。主要是因为if操作和=操作不是原子的。
那么如果解决线程安全呢?加锁就是一个好办法!!!

 private static Singleton instance=null;
    public static Singleton getInstance() {
        //区别当前是首批调用还是后续调用决定要不要加锁
        if(instance==null) {
       //加锁操作
            synchronized (Singleton.class) {
                if(instance==null) {
                    instance=new Singleton();
                }
            }
        }
        return instance;
    }

阻塞队列

阻塞队列是一种特殊的队列。遵循“先进先出”的原则。
在Java标准库中内置了一个BlockingQueue这样的类实现阻塞队列。put阻塞式入队列,take阻塞式出队列。
阻塞队列是一种线程安全的数据结构,并具有以下特征:

  • 当队列为空,尝试出队列就会产生阻塞。一直阻塞到队列里元素不为空
  • 当队列已满,尝试入队列也会产生阻塞。一直阻塞到队列里元素不满为止。

生产者消费者模型

生产者是一组线程,消费者是另一组线程,交易产所是阻塞队列。
生产者消费者模型最大的用途:

  • 解耦合 如A和B两个进行数据传输,此时就是A给B传数据,而如果加上C也让A给C传数据就比较麻烦,此时如果有一个中间媒介存放A的数据,谁需要就从里边取就会方便很多。
  • 削峰填谷 如大坝,遇到汛期时,关闸蓄水避免下游水太多被淹,遇到旱期,开闸放水避免下游缺水出现旱灾。

借助BlockingQueue实现生产者消费者模型代码:

public static void main(String[] args) throws InterruptedException {
        //创建一个阻塞队列
        BlockingQueue<Integer> queue=new LinkedBlockingQueue<>();

        //创建一个生产者
        Thread producer=new Thread() {
            @Override
            public void run() {
                for (int i = 0; i <1000 ; i++) {
                    System.out.println("生产者"+i);
                    try {
                        queue.put(i);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        producer.start();

        //创建一个消费者
        Thread consumer=new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        int num=queue.take();
                        System.out.println("消费者"+num);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        consumer.start();


        producer.join();
        consumer.join();
    }

通过循环队列实现生产者消费者模型

  • [head,tail)表示队列中的有效元素区间
  • 入队时,把新元素放到tail位置上并且tail++。
  • 出队时,把队首元素取出来,并且head++。
  • 使用size记录元素个数,入队size++,出队size–。size=0队列已空,size=数组的最大长度,队列已满。
//使用循环队列实现阻塞队列
     static class BlockingQueue {
         private int[] items=new int[1000];
         private int head=0;
         private int tail=0;
         private int size=0;
         private Object object=new Object();

        //入队列
        private void put(int val) throws InterruptedException{
            synchronized (object) {
                while (size==items.length) {
                    //元素已满 进行阻塞
                    object.wait();
                }
                items[tail]=val;
                tail++;
                if(tail>=items.length) {
                    tail=0;
                }
                size++;
                //唤醒take中的wait
                object.notify();
            }

        }

        //出队列
        public int take() throws InterruptedException {
            int res=0;
            synchronized (object) {
                while (size==0) {
                    //队列已空 无法出队列 进行阻塞
                    object.wait();
                }
                res=items[head];
                head++;
                if(head>=items.length) {
                    head=0;
                }
                size--;
                //唤醒put中的wait
                object.notify();
            }
            return res;
        }
     }

    public static void main(String[] args) throws InterruptedException {
        BlockingQueue blockingQueue=new BlockingQueue();
        //消费者
        Thread consumer=new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        int ret=blockingQueue.take();
                        System.out.println("消费者"+ret);
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        consumer.start();
        //生产者
        Thread producer=new Thread() {
            @Override
            public void run() {
                for (int i = 1; i <1000 ; i++) {
                    System.out.println("生产者"+i);
                    try {
                        blockingQueue.put(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        producer.start();

        consumer.join();
        producer.join();
    }

定时器

相当于“闹钟”,比如浏览器访问网站时,网卡了,浏览器就会转圈圈(阻塞等待),这个等待不是无限等待,等待一定时间就显示访问超时。
Java标准库中,提供了Timer类。Timer类的核心方法是schedule,schedule有两个参数,一个是指定即将执行的任务代码,一个是指定多长时间后执行。

public static void main(String[] args) {
        System.out.println("代码开始执行");
        Timer timer=new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("触发定时器");
            }
        },3000);//3s后执行
    }

实现定时器

一个定时器里面可以安排很多任务,这些任务按照时间,谁时间先到先执行谁。
描述任务:使用Runnable描述任务
组织任务:需要在一堆任务中找到最先要执行的任务。使用带阻塞功能的优先级队列实现。
提供schedule方法往阻塞队列中插入元素。在Timer内部有一个线程扫描队首元素。如果队首元素到时间点了就执行任务,如果没到点,就把队首元素放回队列,继续扫描。

//定时器实现
    static class Task implements Comparable<Task> {
       //command表示任务是什么
        private Runnable command;
        //time表示这个任务什么时候到时间
        private long time;

        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<>();
        //使用locker对象解决盲等
        public Object locker=new Object();

        public void schedule(Runnable command,long delay) {
            Task task=new Task(command,delay);
            queue.put(task);

            // 每次插入新的任务都要唤醒扫描线程. 让扫描线程能够重新计算 wait 的时间, 保证新的任务也不会错过~~
            synchronized (locker) {
                locker.notify();
            }
        }

        public Timer() {
            //创建扫描线程,判断当前任务是不是已经到时间执行
            Thread t=new Thread() {
                @Override
                public void run() {
                    while (true) {
                        //取队首元素判断时间是不是到了
                        try {
                            Task task=queue.take();
                            long curTime=System.currentTimeMillis();
                            if(task.time>curTime) {
                                //时间没有到不执行重新插入队列
                                queue.put(task);
                                //根据时间差来进行等待
                                synchronized (locker) {
                                    locker.wait(task.time-curTime);
                                }
                            } else {
                                //时间到
                                task.run();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            break;
                        }
                    }
                }
            };
            t.start();
        }
    }

线程池

为了解决线程的频繁创建和销毁带来的开销,而引入线程池。当我们需要使用线程时,就从池子里取一个线程。当我们不需要这个线程的时候,就把这个线程还回池子中,这种操作就比创建销毁线程效率高很多。
在Java标准库里提供了标准的线程池ThreadPoolExecutor。其使用相对复杂,里边有很多参数。下面我们对ThreadPoolExecutor里边的参数进行说明:

  • corePoolSize 核心线程数(即一个公司正式员工的数量)。
  • maximumPoolSize 最大线程数(即一个公司正式员工+临时工的数量)。
  • keepAluveTime 描述临时工可以摸鱼多长时间 unit为keepAluveTime的时间单位。
  • workQueue 阻塞队列。
  • threadFactory 线程的创建方式 通过这个参数来设定不同的线程的创建方式。
  • Rejected Execution Handler 拒绝策略 当任务队列满了的时候来了新任务。丢弃最新任务或丢弃最老任务或阻塞等待或抛出异常。

Executors

由于ThreadPoolExecutor使用起来比较复杂,标准库又提供了一组其他类相当于对ThreadPoolExecutor有一层封装。
Executors 相对于一个“工厂类”提供一组工厂方法可以创建不同风格的线程池实例。

  • newFixedThreadPool 创建一个固定线程数量的线程池。
  • newCachedThreadPool 创建一个数量可变的线程池。
  • newSingleThreadPool 创建一个只包含一个线程的线程池。
  • newScheduleThreadPool 设定延时时间的线程池。

线程池的实现

static class Worker extends Thread {
        private BlockingQueue<Runnable> queue = null;

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

        @Override
        public void run() {
            // 工作线程的具体的逻辑.
            // 需要从阻塞队列中取任务.
            while (true) {
                try {
                    Runnable command = queue.take();
                    // 通过 run 来执行这个具体的任务.
                    command.run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class ThreadPool {
        // 包含一个阻塞队列, 用来组织任务.
        private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

        // 这个 list 就用来存放当前的工作线程.
        private List<Thread> workers = new ArrayList<>();

        private static final int MAX_WORKER_COUNT = 10;

        // 通过这个方法, 把任务加入到线程池中.
        // submit 不光可以把任务放到阻塞队列中, 同时也可以负责创建线程.
        public void submit(Runnable command) throws InterruptedException {
            if (workers.size() < MAX_WORKER_COUNT) {
                // 如果当前工作线程的数量不足线程数目上限, 就创建出新的线程.
                // Worker 内部要能够取到队列的内容. 就需要把这个队列实例通过 Worker 的构造方法, 传过去.
                Worker worker = new Worker(queue);
                worker.start();
                workers.add(worker);
            }
            queue.put(command);
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值