【Java_EE】单例模式、阻塞队列、线程池、定时器

发布于:2025-07-05 ⋅ 阅读:(20) ⋅ 点赞:(0)

目录

单例模式

饿汉模式<代码>

懒汉模式<代码>

阻塞队列

阻塞队列概念

阻塞队列解决的问题

阻塞队列工作原理

阻塞队列的优/缺点

优点

缺点

模拟实现阻塞队列<代码>

线程池

线程池概念

线程池解决的问题

线程池参数

四种拒绝策略

线程池工作流程

模拟实现线程池<代码>

定时器

模拟实现定时器<代码>


单例模式

单例设计模式:确保一个类只有一个实例,且单例实例应该为static静态变量,保证全局唯一

单例模式实现方式:

饿汉模式<代码>

class Pattern {
    private static Pattern instance = new Pattern();
    private Pattern() {
        // 设置为私有,目的是外部不能初始化
    }
    public static Pattern getInstance() {
        return instance;
    }
}
public class SinglePatternOne {
    public static void main(String[] args) {
        Pattern instance1 = Pattern.getInstance();
        Pattern instance2 = Pattern.getInstance();
        System.out.println(instance2 == instance1);
    }

}

懒汉模式<代码>

class LazyPattern {
    private volatile static LazyPattern instance;
    private static Object locker = new Object();
    private LazyPattern() {
        // 设置为私有,目的是外部不能初始化
    }

    public static LazyPattern getInstance() {
        if(instance == null) { // 只有第一次调用这个方法才加锁,之后调用就不用再进行加锁

            synchronized (locker) { // 达到原子性效果
                if(instance == null) {
                    instance = new LazyPattern();
                    /**
                     * new操作的指令
                     * 1:内存分配
                     * 2:初始化零值
                     * 3:执行构造函数
                     * 正常顺序:1-》2-》3
                     * 但是编译器优化机制,会导致指令重排序,编程 1-》3-》2
                     * 加上volatile关键字,解决编译器优化问题(内存可见性、指令重排序)
                     */
                }
            }

        }
        return instance;
    }
}
public class SinglePatternTwo {
    public static void main(String[] args) {
        LazyPattern instance1 = LazyPattern.getInstance();
        LazyPattern instance2 = LazyPattern.getInstance();
        System.out.println(instance1 == instance2);
    }
}

阻塞队列

阻塞队列概念

阻塞队列:是线程安全的队列,是多线程编程中生产者-消费者模型的核心组件。支持队列空时阻塞获取操作,队列满时阻塞插入操作

阻塞队列解决的问题

阻塞队列:协调多个线程之间的工作,主要目的是减少锁竞争(锁竞争:多个线程竞争同一个锁。就是在线程1获取到locker还未释放,线程2就去获取locker,线程2就会阻塞)

阻塞队列工作原理

put(element)方法:插入元素,具有阻塞功能,队列满时进行阻塞

take()方法:移除并返回头部元素,具有阻塞功能,队列空时进行阻塞

阻塞队列中,只有这两个方法具有阻塞功能

阻塞队列的优/缺点

优点

减少资源竞争,提高效率

模块之间能更好的“解耦合”

可以做到削峰填谷(如果资源不够用,服务器就挂了)

缺点

系统结构复杂,维护高

引入队列层数太多,就会增加网络开销

模拟实现阻塞队列<代码>

class Queue {
    private int[] queue = null;
    private int head;
    private int tail;
    private int size;
    private Object locker = new Object();

    public Queue(int capacity) {
        this.queue = new int[capacity];
    }

    public void put(int element) throws InterruptedException {
        synchronized (locker) {
            while (size >= queue.length) {
                locker.wait(); // 队列满时,进行阻塞等待
            }

            queue[tail] = element;
            tail++;
            if(tail >= queue.length) {
                tail = 0;
            }
            size++;
            locker.notify();
        }
    }

    public int take() throws InterruptedException {
        synchronized (locker) {
            while (size < queue.length) {
                locker.wait(); // 队列为空时,进行阻塞
            }

            int result = head;
            head++;
            if(head >= queue.length) {
                head = 0;
            }
            size--;
            locker.notify();
            return result;
        }
    }
}

public class MyBlockingQueue {
    public static void main(String[] args) {
        Queue queue = new Queue(10);

        Thread thread1 = new Thread(() -> {
            int i = 0;
            while (i != 400) {
                try {
                    queue.put(i);
                    System.out.println("存入队列:" + i);
                    i++;
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                if(i == 20) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            int result = 0;
            while (true) {
                try {
                    result = queue.take();
                    System.out.println(result);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                if(result == 40) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

线程池

线程池概念

线程池:是一种管理和复用线程的机制,通过预先创建一组线程并统一管理任务执行

线程池解决的问题

  1. 避免频繁创建/销毁线程。降低资源消耗
  2. 任务到达时,直接使用空闲线程,无需等待线程创建。提高响应速度
  3. 防止无限制创建线程导致系统崩溃。管控并发数量
  4. 支持任务排队、拒绝策略、监控功能等。统一任务管理

线程池参数

1:corePoolSize 核心线程数目

2:maximumPoolSize 最大线程数目(最大线程数=核心线程数+临时线程数)

3:keepAliveTime 临时线程在终止前等待新任务的最长时间

4:unit 是keepAliveTime的时间单位(时、分、秒、毫秒)

5:workQueue 任务队列,队列仅保存由execute方法提交的可运行任务

6:threadFactory 线程工厂,执行器创建新线程时使用的工厂

7:handler 拒绝策略,达到线程边界或队列容量时使用拒绝策略

四种拒绝策略

  1. AbortPolicy 中止策略,总是抛出RejectedExecutionException异常
  2. CallerRunsPolicy 提交任务的线程自己执行任务
  3. DiscardOldestPolicy 丢弃队列中最老的一个任务,然后重新尝试提交当前任务
  4. DiscardPolicy 丢弃队列中最新的一个任务,不抛异常不通知

线程池工作流程

模拟实现线程池<代码>

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

class MyThreadPool {
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    // 安全的阻塞队列,用于存放任务
    public void myThreadPool(int threads) {
        for (int i = 0; i < threads; i++) { // 创建线程,用于取任务,并执行任务
            Thread thread = new Thread(() -> {
                try {
                    Runnable task  = queue.take(); // 取任务
                    task.run(); // 执行任务
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
            thread.start();
        }
    }

    public void submit(Runnable task) throws InterruptedException {
        queue.put(task); // 上传任务
    }
}
public class MyFixedThreadPool {
    // 只有核心线程数
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool threadPool = new MyThreadPool();
        threadPool.myThreadPool(10);
//        threadPool.submit(new Runnable() { // 这样只提交了一个任务
//            @Override
//            public void run() {
//                for (int i = 0; i < 20; i++) {
//                    System.out.println("任务:" + i);
//                }
//            }
//        });

        for (int i = 0; i < 30; i++) { // 这样就是多线程抢占式执行
            int id = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("任务:" + id);
                }
            });
        }
    }
}

定时器

定时器:用于在指定时间或周期性地执行任务的工具。能够同时管理多个任务,有一定数据结构来组织这些任务。广泛用于任务调度、超时控制等

模拟实现定时器<代码>

注意:

两个线程操作一个队列,可能引发线程安全问题,所以需要加锁(synchronied)

当队列为空时,线程应该阻塞等待;当任务还没到执行时间时,线程也要阻塞等待

class TimerTask implements Comparable<TimerTask> {
    private Runnable task;
    private long time; // 任务执行时间

    public TimerTask(Runnable task, long delay) {
        this.task = task;
        this.time = System.currentTimeMillis() + delay;
    }

    public Runnable getTask() {
        return task;
    }

    public long getTime() {
        return time;
    }

    // 因为要将类放到优先级队列当中,所以需要给类指定比较规则(1:实现Comparable接口 / 2:自己写一个类,实现Comparator)
    @Override
    public int compareTo(TimerTask o) {
        return (int)(this.time-o.time);
    }
}
class MyTimer {
    private PriorityQueue<TimerTask> queue = new PriorityQueue<>(); // 优先级队列,用于存放任务
    private Object locker = new Object();

    /**
     * submit(xx)方法存放任务 和 此处取任务,执行任务,删除任务 -》是两个线程同时修改一个队列
     * 两个线程同时修改一个队列,可能会引发线程安全问题-》所以需要加锁(synchronied)
     */
    public MyTimer() { // 创建线程,取出任务,执行任务(到时间执行),删除任务
        Thread thread = new Thread(() -> {
            while (true) {
                synchronized (locker) {
                    if (queue.isEmpty()) {
                        try {
                            locker.wait(); // 当队列为空时,就进行阻塞等待。队列中有任务时,就执行下面操作
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    TimerTask timerTask = queue.peek(); // 取出队首任务
                    long curTime = System.currentTimeMillis();
                    if (curTime < timerTask.getTime()) {
                        try {
                            locker.wait(timerTask.getTime() - curTime);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        timerTask.getTask().run(); // 执行任务(到时间执行任务)
                        queue.poll(); // 删除队首任务
                    }
                }
            }
        });
        thread.start();
    }
    public void submit(Runnable task, long delay) {
        synchronized (locker) {
            queue.offer(new TimerTask(task, delay)); // 任务存入队列
            locker.notify();
        }
    }
}

public class Timer {
    public static void main(String[] args) {
        MyTimer myTimer = new MyTimer();
        myTimer.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("4s后执行");
            }
        }, 4000);

        myTimer.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("5s后执行");
            }
        }, 5000);

        myTimer.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("2s后执行");
            }
        }, 2000);

        System.out.println("启动程序");
    }
}