Java内功修炼(3)——并发的四重境界:单例之固、生产消费之衡、定时之准、池化之效

1.单例模式

1.1 概述

单例模式(Singleton Pattern):是一种常用的设计模式,主要用于确保一个类在整个应用程序中只有一个实例,并提供一个全局访问点
核心作用

  • 1.控制资源访问:常用于管理共享资源,可以避免多线程竞争或重复创建资源导致的性能问题

  • 2.全局状态管理:当某些对象需要被多个模块或组件共享时,单例模式提供统一的访问入口

  • 3.保证数据一致性:避免多个实例导致的数据不一致问题
    常见实现方式

  • 饿汉模式:在类加载时就创建实例,避免了线程安全问题,但可能会造成资源浪费,尤其是当实例初始化过程复杂或占用较多资源时

  • 懒汉模式:一种延迟初始化的单例实现方式。实例在第一次被使用时才创建,而非在类加载时就创建。这种方式可以节省资源,但需要考虑线程安全问题

1.2 饿汉模式

java 复制代码
public class Singleton {
	//类加载时进行实例化
    private static final Singleton hungry = new Singleton();
    //全局唯一获取实例的接口
    public static Singleton getInstance(){
        return hungry;
    }
    //构造方法私有化
    private Singleton(){}
}

特点:

  • 类加载时进行实例化
  • 不存在运行时实例化过程,所以不存在线程安全问题

缺点:

  • 即使后面的场景中没有使用到该实例,也会将该实例创建出来,可能会造成不必要的资源浪费

1.3 懒汉模式

java 复制代码
class Singleton{
	//volatile:禁止指令重排序
    private static volatile Singleton lazy = null;
    //创建锁对象
    private static final Object object = new Object();
    //全局唯一获取实例的接口
    public static Singleton getInstance(){
        //外层if判断:优化,提高性能
        if (lazy == null) {
            //避免多线程时实例化多个对象
            synchronized (object) {
                if (lazy == null) {
                    lazy = new Singleton();
                }
            }
        }
        return lazy;
    }
    //构造方法私有化
    private Singleton(){}
}

实现细节:

  • 1.通过synchronized加锁解决线程安全问题
  • 2.外层if判断,减少锁的开销
  • 3.volatile防止指令重排序(避免半初始化对象)

特点:

  • 只有在真正需要时才创建实例,减少系统启动时的资源占用,资源利用率高

缺点:

  • 若未正确使用同步机制(如synchronized或volatile),可能导致多线程环境下实例被多次创建,线程安全实现复杂

1.4 懒汉模式半初始化

Java对象初始化流程

  • 1.内存分配阶段:当使用new关键字创建对象时,JVM会在堆内存中为该对象分配空间

  • 2.对象初始化阶段:对象内存分配完成后,开始执行初始化

  • 3.引用赋值阶段:内存分配完成后,JVM将分配的内存地址赋值给引用变量
    Java对象初始化流程(指令重排序后)

  • 1.内存分配阶段:当使用new关键字创建对象时,JVM会在堆内存中为该对象分配空间

  • 2.引用赋值阶段:内存分配完成后,JVM将分配的内存地址赋值给引用变量

  • 3.对象初始化阶段:对象内存分配完成后,开始执行初始化

1.5 懒汉/饿汉优缺点对比

特性 懒汉模式 饿汉模式
实例化时机 第一次使用时 类加载时
资源消耗 节省资源 可能浪费资源
线程安全 需要额外同步机制 天然线程安全
实现复杂度 较复杂 简单
适用场景 实例化开销大,延迟加载 实例化开销小,不需要延迟加载

2.生产者/消费者模式

2.1 概述

生产者/消费者模式(Producer/consumer model):用于协调多个线程或进程之间的任务分配与数据处理。生产者负责生成数据或任务,消费者负责处理这些数据或任务,二者通过共享的缓冲区(队列)进行解耦,避免直接依赖
核心作用

  • 1.解耦生产与消费逻辑:生产者仅负责生成数据并放入缓冲区,消费者仅从缓冲区获取数据并处理。两者无需直接交互,降低代码复杂度,提高模块化程度
  • 2.平衡处理速率差异:生产者与消费者通常以不同速度运行。缓冲区作为中间层,允许生产者持续写入数据,消费者按自身能力消费,避免互相阻塞
  • 3.削峰填谷:通过缓冲队列平滑流量波动,避免系统因瞬时高负载崩溃。当生产者突然产生大量请求时,缓冲区暂时存储这些请求,消费者按照自身处理能力逐步消费;当生产者速度降低时,缓冲区逐步释放积压的请求,保持消费者稳定工作

2.2 实现阻塞队列

java 复制代码
class MyBlockingQueue{
    private int head = 0;
    private int tail = 0;
    private int useSize = 0;
    private final String[] array;

    public MyBlockingQueue(int capacity){
        array = new String[capacity];
    }
    //添加
    public synchronized void put(String string) throws InterruptedException {
        if (isFull()){
            //队列满了,等待消费者消耗元素
            this.wait();
        }
            array[tail] = string;
            tail++;
            tail = (tail + 1) % array.length;
            useSize++;
            this.notify();
    }
    //删除
    public String take() throws InterruptedException {
        String ret;
        synchronized (this) {
            if (useSize <= 0) {
                //队列空了,等待生产者添加元素.
                this.wait();
            }
            ret = array[head];
            head++;
            head = (head + 1) % array.length;
            useSize--;
            this.notify();
        }
        return ret;
    }
    //判断是否满了
    public boolean isFull(){
        return useSize >= array.length;
    }
}

2.3 实现生产者/消费者模式

java 复制代码
public class Producer_Consumer_Blog {
    public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue(1000);
        Thread thread1 = new Thread(()->{
            int n = 1;
            while (true){
                try {
                    queue.put(n + "");
                    System.out.println("生产元素n = " + n);
                    n++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread thread2 = new Thread(()->{
            while (true){
                try {
                    System.out.println("消费元素n = " + queue.take());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread1.start();
        thread2.start();
    }
}

3.定时器

3.1 概述

定时器(Timer):用于在特定时间间隔或指定时间点执行任务 的编程模式,广泛应用于定时任务调度、延迟操作、周期性任务等场景。核心思想是将任务的执行逻辑与时间控制解耦,通过统一的定时器管理多个任务
核心作用/特点

  • 1.管理异步任务调度:Timer允许你安排一个任务在未来的某个时间点执行,或者以固定的间隔重复执行
  • 2.后台执行:Timer可以使用一个后台线程来执行任务,这意味着调度和执行任务不会阻塞主线程(主线程结束后后台线程跟着结束)
  • 3.简单易用:Timer提供了一个相对简单的方式来处理定时任务,适合用于不需要复杂调度的场景

标准库Timer构造方法

java 复制代码
//1.默认构造方法
//创建一个Timer对象,是一个后台线程,并使用线程的默认名字
public Timer() {
        this("Timer-" + serialNumber());
}
//2.指定线程名字的构造方法
//创建一个Timer对象,是一个后台线程,并使用指定的线程名字
public Timer(String name) {
        thread.setName(name);
        thread.start();
}
//3.指定是否为后台线程的构造方法
//传入true,是后台线程;传入false,是前台线程
public Timer(boolean isDaemon) {
        this("Timer-" + serialNumber(), isDaemon);
}
//4.指定线程名字和是否为后台线程的构造方法
public Timer(String name, boolean isDaemon) {
        thread.setName(name);
        thread.setDaemon(isDaemon);
        thread.start();
}

标准库Timer的schedule方法

  • 1.schedule(TimerTask task, Date time):安排任务在指定的时间执行一次
java 复制代码
public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("延迟三秒执行");
            }
        };
        //使用Date对象来指定具体的执行时间
        //new Date(System.currentTimeMillis()+1000表示当前时间等待1000ms
        timer.schedule(timerTask,new Date(System.currentTimeMillis()+1000));
}
  • 2.schedule(TimerTask task, Date firstTime, long period):安排任务在指定的时间首次执行,然后每隔一段时间重复执行
java 复制代码
public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("延迟三秒执行");
            }
        };
        //当前时间等待1000ms后第一次执行任务
        //此后每间隔1000ms就执行一次任务
        timer.schedule(timerTask,new Date(System.currentTimeMillis()+1000),1000);
}
  • 3.schedule(TimerTask task, long delay):安排任务在指定的延迟时间后执行一次(相对于当前时间)
java 复制代码
public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("延迟三秒执行");
            }
        };
        //当前时间延迟3000ms后执行
        timer.schedule(timerTask,3000);
}
  • 4.schedule(TimerTask task, long delay, long period):安排任务在指定的延迟时间后首次执行,然后每隔一段时间重复执行
java 复制代码
public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("延迟三秒执行");
            }
        };
        //当前时间延迟3000ms后执行
        //此后每间隔3000ms就执行一次任务
        timer.schedule(timerTask,3000,3000);
}

3.2模拟实现定时器

java 复制代码
class MyTask implements Comparable<MyTask>{
    private final Runnable runnable;
    private final long time;
    public MyTask(Runnable runnable,long delay){
        this.runnable = runnable;
        this.time = System.currentTimeMillis() + delay;
    }
    public long getTime(){
        return this.time;
    }
    public void run(){
        runnable.run();
    }
    @Override
    public int compareTo(MyTask o) {
        return (int)(this.time - o.time);
    }
}
class MyTime{
    private final PriorityQueue<MyTask> queue = new PriorityQueue<>();
    public void schedule(Runnable runnable,long delay){
        synchronized (this) {
            MyTask myTask = new MyTask(runnable, delay);
            queue.offer(myTask);
            this.notify();
        }
    }
    public MyTime(){
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    synchronized (this) {
                        while (queue.isEmpty()) {
                            this.wait();
                        }
                        MyTask myTask = queue.peek();
                        long curTime = System.currentTimeMillis();
                        if (curTime >= myTask.getTime()) {
                            myTask.run();
                            queue.poll();
                        } else {
                            this.wait(myTask.getTime() - curTime);
                        }
                    }
                }catch (InterruptedException e){
                    throw new RuntimeException(e);
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
    }
}

4.线程池

4.1 概述

线程池:线程池是一种管理和复用线程的编程模式。它预先创建一定数量的线程,在执行任务需要时,将任务分配给这些线程,从而提高运行效率
核心作用:优化多线程任务的执行效率与管理资源
特点

  • 线程复用:当线程执行完一个任务时,不会立即销毁,而是等待下一个任务的到来(当然这种等待是有时间限制的),这样避免了频繁的创建和销毁线程

  • 动态调整:根据实际环境需要动态调整线程数量,以达到最佳性能

  • 任务队列:线程池会维护一个任务队列,用于存放待执行的任务,当线程空闲时,从队列中取出任务并执行
    标准库线程池构造方法

  • 1.int corePoolSize:核心线程数

  • 2.int maximumPoolSize:最大线程数

  • 3.long keepAliveTime:非核心线程的空闲时的最大存活时间

  • 4.TimeUnit unit:时间单位

  • 5.BlockingQueue< Runnable > workQueue:任务队列

  • 6.ThreadFactory threadFactory:线程工厂,用于创建新线程的工厂

  • 7.RejectedExecutionHandler handler:拒绝策略

4.3线程池的执行流程

假设现在有一个线程池:核心线程数2,最大线程数4,等待队列2

  • 任务数量<=2(A,B)时,由核心线程执行任务
  • 2<任务数量<=4(A,B,C,D)时,核心线程无法同时处理所有任务,未被执行的任务(C,D)将会进入等待队列中等待核心线程执行
  • 4<任务数量<=6(A,B,C,D,E,F),此时等待队列也满了,线程池就会就会开放非核心线程来执行任务,C和D任务继续在等待队列中等待,新添加的E和F任务由非核心线程来执行
  • 任务数量>6,核心线程,等待队列,非核心线程都被任务所占用,仍然无法满足需求,此时就会触发线程池的拒绝策略

4.4 拒绝策略

  • 1.AbortPolicy:直接抛异常
  • 2.CallerRunsPolicy:由提交该任务的线程来执行
  • 3.DiscardPolicy:丢弃新任务
  • 4.DiscardOldestPolicy:丢弃最老的任务

4.5 模拟实现线程池

java 复制代码
public class MyThreadPoolExecutor {
    private final int capacity = 1000;
    //阻塞队列
    private final MyBlockingQueue queue = new MyBlockingQueue(capacity);
    private final List<Thread> list = new ArrayList<>();
    //创建线程
    public MyThreadPoolExecutor(int n){
        for (int i = 0; i < n; i++) {
            Thread thread = new Thread(()->{
                while (true) {
                    try {
                        queue.take().run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            thread.start();
            list.add(thread);
        }
    }
    //添加任务
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
    public int getCapacity(){
        return capacity;
    }
    //获取线程
    public List<Thread> getList() {
        return list;
    }
}
相关推荐
TechNomad6 小时前
设计模式:享元模式(Flyweight Pattern)
设计模式·享元模式
手握风云-6 小时前
JavaEE 进阶第一期:开启前端入门之旅(上)
java·前端·java-ee
一只鱼^_6 小时前
365. 水壶问题(详解)
java·javascript·c++·leetcode·线性回归
Leo来编程7 小时前
设计模式14-组合模式
设计模式·组合模式
趙卋傑7 小时前
Spring原理
java·后端·spring
v_cxsj8137 小时前
基于大数据的京东手机销售数据 可视化分析设计与开发03446原创的定制程序,java、PHP、python、C#小程序、文案全套、毕设程序定制、成品等
java·大数据·智能手机
saddhu.7 小时前
C++ 快速复习指南(上半部分)
java·c++·算法