饿汉模式 – 迫切, 程序启动, 类加载之后, 立即创建出实例
代码示例
class SingletonHungryMode { // 不加以任何限制就是线程安全的
private static SingletonHungryMode instance = new SingletonHungryMode(); // 直接new
public static SingletonHungryMode getInstance() {
return instance;
}
// 添加限制, 让外部无法 new 出对象
private SingletonHungryMode() {
}
}
优点:
缺点:
懒汉模式 - 正在需要用到实例的时候才创建对象
class SingletonLazyMode {
private static SingletonLazyMode instance = null; // 不是直接new
// 这个版本不是线程安全的
public static SingletonLazyMode getInstance() {
if (instance == null) {
instance = new SingletonLazyMode();
}
return instance;
}
// 添加限制, 让外部无法 new 出对象
private SingletonLazyMode() {
}
}
优点:
Java的反射与单例模式的思考
我们都知道, Java提供了反射机制, 通过反射, 我们可以得到类的所有信息, 可以得到private修饰的构造函数, 就可以new 多个对象, 这样单例模式中private修饰;
那么我们通过private修饰构造方法设计的单例模式是不是就存在问题呢? 是的! 使用反射, 确实可以在当前单例模式中, 创建出多个实例;
反射是属于 “非常规” 的编程手段, 正常开发的时候, 不应该使用/慎用; 滥用反射, 会带来极大的风险, 会让代码变的抽象, 难以维护!
Java 中也有实现单例模式而不怕反射的
懒汉模式下线程不安全的原因
解决方案 1)
// 版本2 加锁保证, 但是存在频繁加锁的问题 -- 效率低
public static SingletonLazyMode getInstance() {
synchronized (locker) {
if (instance == null) {
instance = new SingletonLazyMode();
}
}
return instance;
}
解决方案 2)
// 版本3 双重判断, 避免无脑加锁 -- 效率高
public static SingletonLazyMode getInstance() {
if (instance == null) { // 条件判断是否需要加锁
synchronized (locker) {
if (instance == null) { // 条件判断是否需要创建新的对象
instance = new SingletonLazyMode();
}
}
}
return instance;
}
指令重排序可能出现的问题 |
解决方案 3) – 最终版本
private static volatile SingletonLazyMode instance = null; // 不是直接new
// 版本3 双重判断, 避免无脑加锁 -- 效率高
public static SingletonLazyMode getInstance() {
if (instance == null) { // 条件判断是否需要加锁
synchronized (locker) {
if (instance == null) { // 条件判断是否需要创建新的对象
instance = new SingletonLazyMode();
}
}
}
return instance;
}
// 添加限制, 让外部无法 new 出对象
private SingletonLazyMode() {
}
【使用示例】
public static void main(String[] args) throws InterruptedException {
BlockingDeque<String> queue = new LinkedBlockingDeque<>(10);
// put 入队列, take 出队列 -- 这两个方法有阻塞的功能
queue.put("Hello BlockingDeque");
String elem = queue.take();
System.out.println(elem);
elem = queue.take();
System.out.println(elem);
// offer 入队列, poll 出队列 -- 这两个方法没有阻塞的功能
// queue.offer("test");
// System.out.println(queue.poll());
// System.out.println(queue.poll());
}
public class MyBlockingDeque {
// 使用一个 String 类型的数组来保存元素. 假设这里只存 String.
private String[] strings;
// 指向队列的头部
private int head;
// 指向队列的尾部的下一个元素. 总的来说, 队列中有效元素的范围 [head, tail)
// 当 head 和 tail 相等(重合), 相当于空的队列.
private int tail;
// 使用 size 来表示元素个数.
private int size;
private final static int DEFAULT_CAPACITY = 1000;
// 加锁对象
private Object locker;
public MyBlockingDeque() {
this(DEFAULT_CAPACITY);
}
public MyBlockingDeque(int capacity) {
strings = new String[capacity];
head = tail = size = 0;
locker = new Object();
}
public void put(String str) throws InterruptedException {
synchronized (locker) {
// if (isFull()) {
while (isFull()) { // 循环判断, 保证醒来的时候队列不满了
// 队列满, 进行wait等待
locker.wait();
// return;
}
strings[tail] = str;
++tail;
if (tail >= strings.length) {
tail = 0;
}
++size;
locker.notify(); // 生产完, 唤醒消费消费者进行消费
}
}
public String take() throws InterruptedException {
synchronized (locker) {
while (isEmpty()) {
locker.wait(); // 等待生产者生产
// return null;
}
String str = strings[head];
++head;
if (head >= strings.length) {
head = 0;
}
--size;
locker.notify();
return str;
}
}
private boolean isFull() {
return size == strings.length;
}
private boolean isEmpty() {
return size == 0;
}
}
生产者消费者模型的优势
解耦合
削峰填谷
生产者消费者示例 |
public static void main(String[] args) {
BlockingDeque<Integer> queue = new LinkedBlockingDeque<>(100);
Thread consumer = new Thread(() -> {
while (true) {
try {
Integer task = queue.take();
System.out.println("消费:" + task);
// Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread producer = new Thread(() -> {
int count = 0;
while (true) {
try {
Thread.sleep(1000);
queue.put(count);
System.out.println("生产: " + count);
++count;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
consumer.start();
producer.start();
}
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Hello 3");
}
}, 3000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Hello 2");
}
}, 2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("Hello 1");
}
}, 1000);
System.out.println("程序开始运行!");
}
池的作用: 就是提高效率的
线程池的作用
本来, 是需要创建线程/销毁线程; 现在, 是从池子里获取现成的线程, 并且把用完的线程归还到池子中
工程模式
一般创建对象, 都是通过new, 通过构造方法, 但是构造方法, 存在重大缺陷; 构造方法的名字固定是类名, 有的类, 需要有多种不同的构造方法, 但是构造方法的名字有固定, 就只能使用方法重载的方式来实现了, 当时这里存在一定的局限性!
此时工厂模式就可以解决上述的问题了
使用工程模式创建线程池
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
pool.submit(() -> {
System.out.println("Hello thread Pool");
});
}
}
使用 Java原生的线程池构造方法来创建 (重点)
上述两个参数, 做到了既能保证繁忙的时候高效处理任务, 又能保证空间的时候不会浪费资源
这个两个参数, 说明了, 多余的线程, 空间闲时间超过指定的时间阈值, 就可以被销毁了!
创建线程池方法的总结
上述都是创建线程池的手段, 具体用什么方法创建线程池, 主要看的是具体的应用场景
线程池中线程数量的思考
public class MyThreadPool {
BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>(10);
// 通过这个方法, 来把任务添加到线程池中.
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
// n 表示线程池里有几个线程.
// 创建了一个固定数量的线程池.
public MyThreadPool(int n) {
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
while (true) {
try {
// 取出任务, 并执行~~
Runnable runnable = queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
}