【JavaEE】多线程笔记第三天(【多线程案例】单例模式/阻塞队列/定时器/线程池)

目录

一、单例模式(Singleton)

思考:如何实现一个线程安全的单例模式?

二、阻塞队列(BlockingQueue)

Java标准库中的阻塞队列(BlockingDeque)

 实现简单的生产者消费者模型

三、定时器

四、线程池

标准库中的线程池

实现一个线程池


一、单例模式(Singleton)

单例模式能够保证某个类在程序中只存在 唯一一个实例 ,而不会创建出多个实例。【Java本身就是单个实例】

饿汉式:直接就将对象创建出来,不管后面会不会用到,一次性创建完成,后面需要时直接返回。因为在 类加载 的时候就创建好了对象, 后续不需要创建, 相当于 只读操作 ,所以是 线程安全 的.

代码实例:

//通过Singleton这个类实现  饿汉式
class Singleton {
    // instance 是该类的唯一一个实例
    private static Singleton instance = new Singleton();
    //为了防止再获得他的对象(new),将构造方法私有化 此操作禁止其他类中new这个实例
    private Singleton(){
        
    }
    //提供方法让外界拿到 instance 对象
   public static Singleton getInstance(){
        return instance;   //只读  线程安全
    }
}

懒汉式:当需要用时,再创建实例,后续不再创建。所以懒汉式方法有读,有修改对象,而且不是原子的,线程不安全.

代码实例:

//通过Singleton这个类实现  懒汉式
class Singleton {
    // instance 是该类的唯一一个实例
    //volatile  保证内存可见性 不能保证“原子性”
    private static volatile Singleton instance = null;
    //为了防止再获得他的对象(new),将构造方法私有化 此操作禁止其他类中new这个实例
    private Singleton(){
    }
    //提供方法让外界拿到 instance 对象
    //只有在真正需要实例时,才会创建实例
    public static Singleton getInstance(){
        if (instance == null) {
            instance = new Singleton();//涉及修改 读  线程不安全
        }
        return instance;
    }
}

思考:如何实现一个线程安全的单例模式?

加锁(synchronized) ,将读写操作封装成一个 原子操作

//通过Singleton这个类实现  懒汉式——加锁
class Singleton {

    // instance 是该类的唯一一个实例
    //volatile  保证内存可见性 不能保证“原子性”
    private static volatile Singleton instance = null;

    //为了防止再获得他的对象(new),将构造方法私有化 此操作禁止其他类中new这个实例
    private Singleton(){
    }

    //提供方法让外界拿到 instance 对象
    //只有在真正需要实例时,才会创建实例
    public static Singleton getInstance(){
        if (instance == null) {//判断是否要加锁
            synchronized(Singleton.class){
                //判断 是否要创建实例
                if (instance == null){
                    //初始化之后,就不会进行修改 线程安全
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

【JavaEE】多线程笔记第三天(【多线程案例】单例模式/阻塞队列/定时器/线程池)_第1张图片

这样看起来已经解决问题了,但是 频繁的获取和释放锁对象,会造成很大开销,所以在外层加上新判断,对已经创建好了的实例,就不去竞争锁,直接返回。

【JavaEE】多线程笔记第三天(【多线程案例】单例模式/阻塞队列/定时器/线程池)_第2张图片

当多个线程同时读取 instance, 会产生 内存可见性 问题,当还没有创建好对象,多个线程读到的都是null,即使 多个线程中有一个线程创建了 instance 实例,其他线程还是会去竞争锁对象所以加上 volatile 关键字避免无用的锁竞争。(禁止指令重排序)

二、阻塞队列(BlockingQueue)

 阻塞队列是一种特殊的队列(线程安全),除了具有队列的性质外(先进先出),还具有阻塞的功能。 典型实例——生产者消费模型

请添加图片描述

  • 当队列满时,继续入队会阻塞,直到有其他线程从队列中取出元素
  • 当队列空时,继续出队会阻塞,直到有其他线程往队列中插入元素

生产者消费者模型就是通过一个容器来解决生产者和消费者强耦合问题。

  1. 阻塞队列 相当于一个 缓冲区 ,平衡了生产者和消费者的处理能力 

     ——比如 “双十一” 当天,淘宝的服务器同一时刻可能会收到大量的支付请求。如果直接处理这些支付请求,可能扛不住。就将这些请求放到一个阻塞队列中,然后再由消费者线程慢慢来处理每个请求。这样做可以有效进行 “削峰”,防止服务器被突然到来的一波请求直接冲垮。


Java标准库中的阻塞队列(BlockingDeque)

  • BlockingDeque 是一个接口, 真正的实现类是 LinkedBlockingQueue
  • put 方法用于 阻塞队列 的 入队列,take 方法用于 阻塞队列 的 出队列
  • BlockingDeque 也有 offer ,poll ,peek 等方法,但是这些方法不带有阻塞特性
public class 阻塞队列 {
    public static void main(String[] args) throws InterruptedException {
        BlockingDeque queue = new LinkedBlockingDeque<>();
        queue.put("孙宇航");   //入队列
        String s = queue.take(); //出队列
        System.out.println(s);
    }
}

实现一个阻塞队列: 普通循环队列 + 线程安全(synchronized) + 阻塞功能(wait 、notify)

  • 当队列满时,put 操作阻塞等待(wait),直到被 take 的操作 (notify)唤醒
  • 当队列空时,take 操作阻塞等待(wait),直到被 put 的操作 (notify)唤醒

    【JavaEE】多线程笔记第三天(【多线程案例】单例模式/阻塞队列/定时器/线程池)_第3张图片

代码实例:

public class 阻塞队列 {
    //实现一个阻塞队列
    //数组————循环队列
    private int[] data = new int[100];
    private int size = 0;
    private int head;
    private int tail;

    //入队列
    //每个操作都在操作公共变量,就给整个方法加锁
    public synchronized void put(int val) throws InterruptedException {
        if (size == data.length){
            //队满 阻塞
            wait();
        }
        data[tail++] =val;
        //tail 达到末尾
        if (tail >= data.length){
            tail = 0;//防止内存溢出
        }
        size++;
        //入队成功,队列非空 唤醒 take()
        //如果 take() 处于 阻塞,就能唤醒,不处于阻塞,也没有副作用
        notify();
    }

    //出队列
    public synchronized Integer take() throws InterruptedException {
        if (size == 0){
            wait();
        }
        int val =data[head];
        head++;
        if (head == data.length){
            head = 0;//防止内存溢出
        }
        size--;
        //take之后,队列非满 唤醒put的等待
        notify();
        return val;
    }
}

 实现简单的生产者消费者模型

public class 阻塞队列 {
    //实现一个阻塞队列
    //数组————循环队列
    private int[] data = new int[100];
    private int size = 0;
    private int head;
    private int tail;

    //入队列
    //每个操作都在操作公共变量,就给整个方法加锁
    public synchronized void put(int val) throws InterruptedException {
        if (size == data.length){
            //队满 阻塞
            wait();
        }
        data[tail++] =val;
        //tail 达到末尾
        if (tail >= data.length){
            tail = 0;//防止内存溢出
        }
        size++;
        //入队成功,队列非空 唤醒 take()
        //如果 take() 处于 阻塞,就能唤醒,不处于阻塞,也没有副作用
        notify();
    }

    //出队列
    public synchronized Integer take() throws InterruptedException {
        if (size == 0){
            wait();
        }
        int val =data[head];
        head++;
        if (head == data.length){
            head = 0;//防止内存溢出
        }
        size--;
        //take之后,队列非满 唤醒put的等待
        notify();
        return val;
    }

    private static 阻塞队列 queue = new 阻塞队列();

    public static void main(String[] args) {

        //生产者线程
        Thread producer = new Thread(()->{
           int num = 0;
           while (true){
               System.out.println("生产了"+num +"个产品");
               try {
                   queue.put(num);
                   num++;
                   //生产慢
                   Thread.sleep(500);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        producer.start();

        //消费者线程
        Thread customer = new Thread(()->{
           while (true){
               //消费慢
               try {
                   Thread.sleep(500);
                   int num = queue.take();
                   System.out.println("消费了"+num+"个产品");
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        customer.start();


    }
}

【JavaEE】多线程笔记第三天(【多线程案例】单例模式/阻塞队列/定时器/线程池)_第4张图片

 消费 < 生产

三、定时器

什么是定时器?

定时器是软件开发中的重要组件,类似于一个”闹钟“,达到一个设定的时间之后,就执行某个指定的代码

标准库中提供了一个 Timer 类,Tmier类的核心方法为 schedule

schedule 包含两个参数,第一个参数指定要执行的任务,第二个参数指定多长时间后执行 (毫秒)

【JavaEE】多线程笔记第三天(【多线程案例】单例模式/阻塞队列/定时器/线程池)_第5张图片

代码实例:

import java.util.Timer;
import java.util.TimerTask;

public class 定时器 {
    public static void main(String[] args) {
        Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("孙宇航");
            }
        },5000);


        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定时器");
                System.out.println();
            }
        },3000);
        System.out.println("开始计时");
    }
}

【JavaEE】多线程笔记第三天(【多线程案例】单例模式/阻塞队列/定时器/线程池)_第6张图片

可以看出 执行完任务后,进程没有退出   Timer内部需要一组线程来执行注册的任务  这里的线程是”前台线程“ 会影响进程退出

前台线程:阻止进程退出    后台线程:(isDaemon)守护线程,进行执行完就会被销毁

 Timer类的实现:

管理多个任务:

描述一个任务(创建一个类MyTask)

//表示一个任务
class MyTask {
    //任务具体干啥
    private Runnable runnable;
    //任务什么时候干
    private long time;
    
    //delay 是一个时间间隔 不是绝对时间
    
    public MyTask(Runnable runnable,long delay){
        this.runnable = runnable;
        this.time = System.currentTimeMillis() +delay;
    }
    public void run(){
        //通过这个方法 执行任务
        runnable.run();
    }
    public long getTime(){
        return this.time;
    }
}

组织一个任务(数据结构)

假设有很多任务,10min后写作业,20min后打游戏,200min后出去玩。我们应该用什么数据结构组织能按时间顺序拿到任务呢?
按照时间先后执行 – 优先级队列(堆) 

因为堆要进行比较,所以放入的元素如果是自定义类型,要指定比较方式,所以让MyTask类 实现Comparable接口,并且重写compareTo()方法指定比较方式(按照时间顺序)

//表示一个任务
class MyTask implements Comparable{
    //任务具体干啥
    private Runnable runnable;
    //任务什么时候干
    private long time;

    //delay 是一个时间间隔 不是绝对时间

    public MyTask(Runnable runnable,long delay){
        this.runnable = runnable;
        this.time = System.currentTimeMillis() +delay;
    }
    public void run(){
        //通过这个方法 执行任务
        runnable.run();
    }
    public long getTime(){
        return this.time;
    }

    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time - o.time);
    }
}

【JavaEE】多线程笔记第三天(【多线程案例】单例模式/阻塞队列/定时器/线程池)_第7张图片

重写compareTo ,指定比较方式

在MyTimer类中,使用一个 带有阻塞功能的优先级队列 来放入任务

class MyTimer {
    //带有阻塞功能的优先级队列  要考虑线程安全 可能多个线程进行注册任务,同时还有线程来执行
    private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
}

schedule方法——按照传入参数,封装一个MyTask对象,放入优先级队列中

// 把任务放进队列
public void schedule(Runnable runnable,long delay){
        MyTask task = new MyTask(runnable, delay);
        queue.put(task);
    }

执行时间到了的任务——需要有一个线程去扫描检查是否任务到了执行时间

private Object locker = new Object();
    //需要执行最靠前的任务
    //选哟有一个线程来检查 小根堆的顶元素 看是否被执行
    public MyTimer(){
        Thread t = new Thread(() -> {
            while (true){
                try {
                    //取出堆顶任务
                    MyTask task = queue.take();
                    //看是否到执行时间
                    long curTime = System.currentTimeMillis();
                    //如果时间还没到
                    if (curTime < task.getTime()){
                        //将任务塞回优先级队列
                        queue.put(task);
                        //等待相应时间 wait()
                        synchronized (locker){
                            locker.wait(task.getTime() - System.currentTimeMillis());
                        }
                    }else {
                        //时间到了——执行该任务
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }

 【JavaEE】多线程笔记第三天(【多线程案例】单例模式/阻塞队列/定时器/线程池)_第8张图片

 可以看到,新任务的执行时间更靠前,所以需要唤醒一下线程,让他再扫描一下堆顶元素,看是否到执行时间了,所以在schedule方法中,需要执行唤醒操作

public void schedule(Runnable runnable,long delay){
        MyTask task = new MyTask(runnable, delay);
        queue.put(task);
        synchronized (locker){
            locker.notify();
        }
    }

整体代码:

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.PriorityBlockingQueue;

//表示一个任务
class MyTask implements Comparable{
    //任务具体干啥
    private Runnable runnable;
    //任务什么时候干
    private long time;

    //delay 是一个时间间隔 不是绝对时间

    public MyTask(Runnable runnable,long delay){
        this.runnable = runnable;
        this.time = System.currentTimeMillis() +delay;
    }
    public void run(){
        //通过这个方法 执行任务
        runnable.run();
    }
    public long getTime(){
        return this.time;
    }

    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time - o.time);
    }
}

class MyTimer {
    //带有阻塞功能的优先级队列  要考虑线程安全 可能多个线程进行注册任务,同时还有线程来执行
    private PriorityBlockingQueue queue = new PriorityBlockingQueue<>();
    public void schedule(Runnable runnable,long delay){
        MyTask task = new MyTask(runnable, delay);
        queue.put(task);
        synchronized (locker){
            locker.notify();
        }
    }
    private Object locker = new Object();
    //需要执行最靠前的任务
    //选哟有一个线程来检查 小根堆的顶元素 看是否被执行
    public MyTimer(){
        Thread t = new Thread(() -> {
            while (true){
                try {
                    //取出堆顶任务
                    MyTask task = queue.take();
                    //看是否到执行时间
                    long curTime = System.currentTimeMillis();
                    //如果时间还没到
                    if (curTime < task.getTime()){
                        //将任务塞回优先级队列
                        queue.put(task);
                        //等待相应时间 wait()
                        synchronized (locker){
                            locker.wait(task.getTime() - System.currentTimeMillis());
                        }
                    }else {
                        //时间到了——执行该任务
                        task.run();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}
//把任务放进队列


public class 定时器 {
    public static void main(String[] args) {
       Timer timer = new Timer();

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
//                System.out.println("孙宇航");
//            }
//        },5000);
//
//
//        timer.schedule(new TimerTask() {
//            @Override
//            public void run() {
//                System.out.println("定时器");
//                System.out.println();
//            }
//        },3000);
//        System.out.println("开始计时");
//    }
}

四、线程池

简单地说 线程池 就是 ——把线程创建好,放在池子里。线程用完了,不还给系统,而是继续放回池子里,以备下一次使用。后面需要用线程时,不必从系统申请,而是直接从池子中拿一定程度上减少了开销,提高了效率。

标准库中的线程池

【JavaEE】多线程笔记第三天(【多线程案例】单例模式/阻塞队列/定时器/线程池)_第9张图片

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class 线程池 {
    public static void main(String[] args) {
        //创建一个固定线程的线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        //创建一个自动扩容的线程池
        Executors.newCachedThreadPool();
        //创建一个只有一个线程的线程池
        Executors.newSingleThreadExecutor();
        //创建一个带有定时器功能的线程池
        Executors.newScheduledThreadPool();

        for (int i = 0; i < 100; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("你好 线程池");
                }
            });
        }
    }
}

实现一个线程池

//描述一个任务(Runnable)
//组织任务(BlockingDeque)
    private static BlockingDeque queue = new LinkedBlockingDeque<>();
/描述工作线程
//工作线程的功能时从队列中取任务来执行
static class Worker extends Thread{
        @Override
        public void run() {
            //拿到上面的队列,取出任务执行
            while (true){
                try {
                    //循环获取任务
                    //如果队列空,则会阻塞
                    Runnable runnable = queue.take();
                    runnable.run();//执行该任务
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
//组织工作线程
//创建一个数据结构来组织线程
 private List workers = new ArrayList<>();
    public MyThreadPool(int n) {
        //创建若干个线程,放到上述数组
        for (int i = 0; i < n; i++) {
            Worker worker = new Worker();
            worker.start();
            workers.add(worker);
        }
    }
//需要实现往线程池添加任务
//创建一个方法,允许程序员放任务到线程池
public void submit(Runnable runnable){
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

class MyThreadPool {
    //描述一个任务(Runnable)
    //组织任务(BlockingDeque)
    private static BlockingDeque queue = new LinkedBlockingDeque<>();
    //描述工作线程
    //工作线程的功能时从队列中取任务来执行
    static class Worker extends Thread{
        @Override
        public void run() {
            //拿到上面的队列,取出任务执行
            while (true){
                try {
                    //循环获取任务
                    //如果队列空,则会阻塞
                    Runnable runnable = queue.take();
                    runnable.run();//执行该任务
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //组织工作线程
    //创建一个数据结构来组织线程
    private List workers = new ArrayList<>();
    public MyThreadPool(int n) {
        //创建若干个线程,放到上述数组
        for (int i = 0; i < n; i++) {
            Worker worker = new Worker();
            worker.start();
            workers.add(worker);
        }
    }
    //需要实现往线程池添加任务
    //创建一个方法,允许程序员放任务到线程池
    public void submit(Runnable runnable){
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

你可能感兴趣的:(JavaEE,单例模式,java,阻塞队列,定时器,线程池)