单例模式(Singleton Pattern)是 Java 最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类也只提供一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式具体的实现有以下两种:
饿汉模式(线程安全)
class Singleton {
// 被 static 修饰,则该类的对象只有一份(并且完成了初始化)
private static Singleton instance = new Singleton();
// 构造方法设为私有,保证外部类无法调用进行构造
private Singleton(){}
// 外部类唯一得到该类对象的方法
public static Singleton getInstance(){
return instance;
}
}
懒汉模式(线程不安全)
class Singleton {
// 被 static 修饰,则该类的对象只有一份(未完成初始化)
private static Singleton instance = null;
// 构造方法设为私有,保证外部类无法调用进行构造
private Singleton(){}
// 外部类唯一得到该类对象的方法
public static Singleton getInstance(){
// 如果该类未有对象则进行创建
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉模式和懒汉模式的区别:
针对上述线程不安全的懒汉模式的代码,可以通过下面几个操作来解决:
class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
阻塞队列(BlockingQueue)是一种特殊的队列,它遵循先进先出的原则。
阻塞队列是一种线程安全的数据结构,并且具有以下特性:
阻塞队列的作用:
使用阻塞队列使我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,BlockingQueue 会帮我们负责。
在 Java 标准库中内置了阻塞队列 BlockingQueue,它是一个接口,底下有七个实现类:
| 实现类 | 说明 |
|---|---|
ArrayBlockQueue | 由数组结构组成的有界阻塞队列 |
LinkedBlockingQueue | 由链表结构组成的有界的阻塞队列(有界,默认大小 Integer.MAX_VALUE,相当于无界) |
PriorityBlockQueue | 支持优先级排序的无界阻塞队列 |
DelayQueue | 使用优先级队列实现的延迟无界阻塞队列 |
SynchronousQueue | 不存储元素的阻塞队列,即单个元素的队列,生产一个,消费一个,不存储元素,不消费不生产 |
LinkedTransferQueue | 由链表结构组成的无界阻塞队列 |
LinkedBlockingDeque | 由链表结构组成的双向阻塞队列 |
BlockingQueue 核心方法:
| 方法 | 说明 |
|---|---|
put(e) | 将元素 e 阻塞式的入队列 |
take() | 用于阻塞式的出队列 |
BlockingQueue 也有其它方法,但是都不带有阻塞特性。
接下来将基于数组实现一个阻塞队列。
// 通过数组实现阻塞队列
class MyBlockingQueue<T>{
// 初始化队列大小
private T[] items = (T[]) new Object[1000];
// 队列中有效元素的个数
private int size = 0;
// 记录队首的位置
private int head = 0;
// 记录队尾的位置
private int tail = 0;
// 入队列
public void put(T e) throws InterruptedException {
synchronized (this) {
if(size == items.length){
// 阻塞等待
this.wait();
}
if (tail == items.length) {
tail = 0;
}
items[tail++] = e;
size++;
// 唤醒 take 的阻塞等待
this.notify();
}
}
// 出队列
public T take() throws InterruptedException {
synchronized (this) {
if(size == 0) {
// 阻塞等待
this.wait();
}
if (head == items.length) {
head = 0;
}
size--;
// 唤醒 put 的阻塞等待
this.notify();
return items[head++];
}
}
}
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者也不找生产者要数据,而是直接通过从阻塞队列里取。
生产者消费者模型的作用:
代码展示生产者消费者模型:
// 使用阻塞队列作为交易场所
private static MyBlockingQueue queue = new MyBlockingQueue();
public static void main(String[] args) throws InterruptedException {
// 生产者
Thread producer = new Thread(() -> {
int num = 1;
while (true){
try {
queue.put(num);
System.out.println("生产者生产了:" + num);
num++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
// 消费者
Thread customer = new Thread(() -> {
while (true){
try {
System.out.println("消费者消费了:" + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
customer.start();
}
定时器是软件开发中的一个重要组件,类似于一个闹钟,当达到设定的时候后,就执行某个具体的任务。
Java 标准库中提供了一个 Time 类作为定时器,它有一个核心方法 schedule,能够在指定多长时间后执行某个任务。schedule(TimerTask taks, long delay) 包含两个参数,第一个参数是即将要执行的代码(通过 TimerTask 重写 run 方法来完成),第二个参数是指定多长事件之后执行(单位是毫秒)。
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("3s 后执行指定任务");
}
}, 3000);
实现定时器,需要完成以下几个工作:
class MyTimer {
// 使用这个类表示当前的一个任务
static class Task implements Comparable<Task>{
// 具体要执行的任务
private Runnable runnable;
// 多久后执行该任务
private long delay;
public Task(Runnable runnable, long delay){
this.runnable = runnable;
this.delay = System.currentTimeMillis() + delay;
}
public void run(){
runnable.run();
}
@Override
public int compareTo(Task o) {
return (int)(this.getDelay() - o.getDelay());
}
public Runnable getRunnable() {
return runnable;
}
public void setRunnable(Runnable runnable) {
this.runnable = runnable;
}
public long getDelay() {
return delay;
}
public void setDelay(long delay) {
this.delay = delay;
}
}
// 通过带有优先级的阻塞队列存储任务,并且通过小根堆能够快速找到时间优先的任务
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<Task>();
// 通过该方法往定时器中注册一个任务
public void schedule(Runnable runnable, long delay){
tasks.put(new Task(runnable, delay));
}
// 创建一个扫描线程,不停的扫描队首元素,判定任务是否可以执行
// 在初始化 MyTimer 时,将扫描线程创建出来
public MyTimer(){
Thread scanner = new Thread(() -> {
while(true) {
if (tasks.size() != 0) {
try {
Task task = tasks.take();
if (task.getDelay() > System.currentTimeMillis()) {
// 时间还没到
tasks.put(task);
synchronized (this){
this.wait(task.getDelay() - System.currentTimeMillis());
}
}else {
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
scanner.start();
}
}