实现一个线程安全的单例模式
单例模式是一种设计模式
什么设计模式?(大概理解为一种棋谱,里面有很多的下棋套路)
单例模式:
要求我们代码中的某个类,只能有一个实例,不能有多个实例。
实例就是对象。
就是说某个类只能new 一个对象,不能new多个对象。
有两种典型的实现:
1:饿汉模式
举一个通俗点的例子:洗碗
中午要吃一顿饭,需要四个碗,那么我们吃完之后立即就把四个碗洗了;
2:懒汉模式
中午要吃一顿饭,需要四个碗,我们吃完之后先不洗,晚上吃饭只需要两个碗,那我们只洗两个碗
饿汉的单例模式,是比较着急的去进行创建实例,
而懒汉模式的单例模式,是不太着急的去创建实例,只是在用的时候去真正的创建
(计算机中懒汉模式比饿汉模式更加高效)
饿汉模式实现
// 通过 Singleton 这个类来实现单例模式. 保证 Singleton 这个类只有唯一实例
// 饿汉模式
class Singleton {
// 1. 使用 static 创建一个实例, 并且立即进行实例化.
// 这个 instance 对应的实例, 就是该类的唯一实例.
private static Singleton instance = new Singleton();
// 2. 为了防止程序猿在其他地方不小心的 new 这个 Singleton, 就可以把构造方法设为 private
private Singleton() {
}
// 3. 提供一个方法, 让外面能够拿到唯一实例.
public static Singleton getInstance() {
//仅仅是读取了变量的内容,如果多个线程只读同一个变量,不修改,此时仍然是线程安全的
return instance;
}
}
public class Demo19 {
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
// Singleton instance2 = new Singleton();
}
}
懒汉模式实现
加锁那里,双重if那里重点理解一下
// 实现单例模式 - 懒汉模式
class Singleton2 {
// 1. 就不是立即就初始化实例.
private static volatile Singleton2 instance = null;
// 2. 把构造方法设为 private
private Singleton2() {
}
// 3. 提供一个方法来获取到上述单例的实例
// 只有当真正需要用到这个 实例 的时候, 才会真正去创建这个实例.
public static Singleton2 getInstance() {
// 如果这个条件成立, 说明当前的单例未初始化过的, 存在线程安全风险, 就需要加锁~~
if (instance == null) {
synchronized (Singleton2.class) {
if (instance == null) {
//两个if一样只是一个美丽的巧合
instance = new Singleton2();//只有真正用到getInstance的时候才会真的创建实例
}
}
}
return instance;
}
}
public class Demo20 {
public static void main(String[] args) {
Singleton2 instance = Singleton2.getInstance();
}
}
实现一个阻塞队列
队列:先进先出
阻塞队列同样也是符合先进先出的规则的队列,比普通队列有点不同:
1:线程安全
2:产生阻塞效果
(1)如果队列为空,尝试出队列,就会出现阻塞,阻塞到队列不为空为止.
(2)如果队列为满,尝试入队列,也会出现阻塞,阻塞到队列不为满为止.
基于上诉的特性,就可以实现"生产者消费者模型"
此处的阻塞队列 我们可以把它比作"交易场所";
交易场所
(就像过年大家吃饺子,往往需要多人合作,大致分为三分(1:和面 ==2:擀饺子皮 == 3:煮饺子))
假设有 A B C三个人一起来
1:如果三个人干的都是擀一个皮包一个饺子(存在锁冲突的问题(一般擀面杖只有一个))
2:这样来:A 专门擀饺子皮,B和C专门负责包
那么A 就是饺子皮的生产者,B C就是饺子皮的消费者
然后对于放饺子皮的那个"盖帘"就是"交易场所"
生产者消费者模型,是实际开发中非常常用的一种多线程开发手段!!!尤其是在服务器开发的场景中
模拟实现
class MyBlockingQueue {
// 保存数据的本体
private int[] data = new int[1000];
// 有效元素个数
private int size = 0;
// 队首下标
private int head = 0;
// 队尾下标
private int tail = 0;
// 专门的锁对象
private Object locker = new Object();
// 入队列
public void put(int value) throws InterruptedException {
synchronized (locker) {
if (size == data.length) {
// 队列满了. 暂时先直接返回.
// return;
locker.wait();
}
// 把新的元素放到 tail 位置上.
data[tail] = value;
tail++;
// 处理 tail 到达数组末尾的情况
if (tail >= data.length) {
tail = 0;
}
// tail = tail % data.length;
size++; // 千万别忘了. 插入完成之后要修改元素个数
// 如果入队列成功, 则队列非空, 于是就唤醒 take 中的阻塞等待.
locker.notify();
}
}
// 出队列
public Integer take() throws InterruptedException {
synchronized (locker) {
if (size == 0) {
// 如果队列为空, 就返回一个非法值.
// return null;
locker.wait();
}
// 取出 head 位置的元素
int ret = data[head];
head++;
if (head >= data.length) {
head = 0;
}
size--;
// take 成功之后, 就唤醒 put 中的等待.
locker.notify();
return ret;
}
}
}
public class Demo22 {
private static MyBlockingQueue queue = new MyBlockingQueue();
public static void main(String[] args) {
// 实现一个简单的生产者消费者模型
Thread producer = new Thread(() -> {
int num = 0;
while (true) {
try {
System.out.println("生产了: " + num);
queue.put(num);
num++;
// 当生产者生产的慢一些的时候, 消费者就得跟着生产者的步伐走.
// Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
Thread customer = new Thread(() -> {
while (true) {
try {
int num = queue.take();
System.out.println("消费了: " + num);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
customer.start();
// 简单验证看这个队列是否能正确工作.
// MyBlockingQueue queue = new MyBlockingQueue();
// queue.put(1);
// queue.put(2);
// queue.put(3);
// queue.put(4);
// int ret = 0;
// ret = queue.take();
// System.out.println(ret);
// ret = queue.take();
// System.out.println(ret);
// ret = queue.take();
// System.out.println(ret);
// ret = queue.take();
// System.out.println(ret);
}
}
定时器
像是一个闹钟,进行定时,在一定时间后,并唤醒并执行某个之前设定好的任务
定时器的构成:
一个带优先级的阻塞队列
队列中的每个元素是一个 Task 对象.
Task 中带有一个时间属性, 队首元素就是即将
同时有一个 worker 线程一直扫描队首元素, 看队首元素是否需要执行
import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;
// 创建一个类, 表示一个任务.
class MyTask implements Comparable<MyTask> {
// 任务具体要干啥
private Runnable runnable;
// 任务具体啥时候干. 保存任务要执行的毫秒级时间戳
private long time;
// after 是一个时间间隔. 不是绝对的时间戳的值
public MyTask(Runnable runnable, long delay) {
this.runnable = runnable;
this.time = System.currentTimeMillis() + delay;
}
public void run() {
runnable.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(MyTask o) {
// 到底是谁见谁, 才是一个时间小的在前? 需要咱们背下来.
return (int) (this.time - o.time);
}
}
class MyTimer {
// 定时器内部要能够存放多个任务
private PriorityBlockingQueue<MyTask> 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);
// 指定一个等待时间
synchronized (locker) {
locker.wait(task.getTime() - curTime);
}
} else {
// 时间到了, 执行这个任务
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
public class Demo24 {
public static void main(String[] args) {
MyTimer myTimer = new MyTimer();
myTimer.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello timer!");
}
}, 3000);
System.out.println("main");
}
}
线程池
进程频繁创建销毁开销比较大,解决方案:进程池 or 线程
线程,虽然比进程轻了不少,但频繁创建销毁开销也比较大,解决方案:线程池 or 协程
线程池:把线程提前创建好,放到池子中~~后面需要线程时,直接从池子中取,就不必去系统那边申请了,用完还给池子就行了
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class MyThreadPool {
// 1. 描述一个任务. 直接使用 Runnable, 不需要额外创建类了.
// 2. 使用一个数据结构来组织若干个任务.
private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
// 3. 描述一个线程, 工作线程的功能就是从任务队列中取任务并执行.
static class Worker extends Thread {
// 当前线程池中有若干个 Worker 线程~~ 这些 线程内部 都持有了上述的任务队列.
private BlockingQueue<Runnable> queue = null;
public Worker(BlockingQueue<Runnable> queue) {
this.queue = queue;
}
@Override
public void run() {
// 就需要能够拿到上面的队列!!
while (true) {
try {
// 循环的去获取任务队列中的任务.
// 这里如果队列为空, 就直接阻塞. 如果队列非空, 就获取到里面的内容~~
Runnable runnable = queue.take();
// 获取到之后, 就执行任务.
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 4. 创建一个数据结构来组织若干个线程.
private List<Thread> workers = new ArrayList<>();
public MyThreadPool(int n) {
// 在构造方法中, 创建出若干个线程, 放到上述的数组中.
for (int i = 0; i < n; i++) {
Worker worker = new Worker(queue);
worker.start();
workers.add(worker);
}
}
// 5. 创建一个方法, 能够允许程序猿来放任务到线程池中.
public void submit(Runnable runnable) {
try {
queue.put(runnable);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Demo26 {
public static void main(String[] args) {
MyThreadPool pool = new MyThreadPool(10);
for (int i = 0; i < 100; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello threadpool");
}
});
}
}
}