单例模式
最后更新于:2022-08-13 13:24:40
定义
保证一个类仅有一个实例,并提供一个全局访问点
类型
创建型
适用场景
想保证任何情况下都绝对只有一个实例
优点
- 在内存里只有一个实例,减少了内存开销
- 可以避免对资源的多重占用
- 设置全局访问点,严格控制访问
缺点
没有接口,扩展困难
代码
懒汉式单例
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
public static LazySingleton getLazySingleton() {
if (lazySingleton == null) {
// 多线程时,当线程1走到这一步时,线程2走到了上一步判断,
// 因为这一步还没有走完,所以线程2也会进入这一步,因此线程是不安全的
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
测试
public class Test {
public static void main(String[] args) throws InterruptedException {
LazySingleton lazySingleton = LazySingleton.getLazySingleton();
System.out.println(lazySingleton);
LazySingleton lazySingleton2 = LazySingleton.getLazySingleton();
System.out.println(lazySingleton);
}
}
结果
lazySingleton为NUll,创建了lazySingleton
com.honor.design.pattern.creational.singleton.LazySingleton@14ae5a5
com.honor.design.pattern.creational.singleton.LazySingleton@14ae5a5
懒汉式注重延迟加载,只有使用的时候才开始初始化,不使用不会初始化,且单线程中只会实例化一次。
接下来是多线程测试
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
public static LazySingleton getLazySingleton(int num) throws InterruptedException {
if (lazySingleton == null) {
Thread.sleep(num);
lazySingleton = new LazySingleton();
System.out.println("lazySingleton为Null,需要初始化");
}
return lazySingleton;
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread T1 = new Thread(() -> {
LazySingleton lazySingleton = LazySingleton.getLazySingleton(100);
System.out.println(Thread.currentThread().getName() + ":" + lazySingleton);
});
Thread T2 = new Thread(() -> {
LazySingleton lazySingleton = LazySingleton.getLazySingleton(50);
System.out.println(Thread.currentThread().getName() + ":" + lazySingleton);
});
ExecutorService service = Executors.newFixedThreadPool(5);
service.submit(T1);
service.submit(T2);
Thread.sleep(400);
service.shutdownNow();
}
}
结果
lazySingleton为Null,需要初始化
pool-1-thread-2:com.design.pattern.creational.singleton.LazySingleton@5840f74e
lazySingleton为Null,需要初始化
pool-1-thread-1:com.design.pattern.creational.singleton.LazySingleton@278b7126
可以看出,在多线程的情况下,创建了多个单例,如果线程更多,就会创建更多,而且,还会出现多个不一样的结果
修改方案
- 使用synchronized
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
// 在静态方法中使用锁,等于锁住了当前的类
public static synchronized LazySingleton getLazySingleton(int num) {
if (lazySingleton == null) {
try {
Thread.sleep(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
运行结果
lazySingleton为Null,需要初始化
pool-1-thread-2:com.honor.design.pattern.creational.singleton.LazySingleton@56cc009c
pool-1-thread-1:com.honor.design.pattern.creational.singleton.LazySingleton@56cc009c
加锁后,只实例化了一次,且结果都是同一个实例。
但是使用synchronized是锁住了类,而且加锁解锁时,性能都有所消耗。
- DoubleCheck双重检查
public class LazyDoubleCheckSingleton {
// 添加volatile ,使得线程的时候,CPU也可以共享内存,
//所有线程都可以看到共享内存的最新状态,保证了内存的可见性,从而避免的重排序
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton() {
}
public static LazyDoubleCheckSingleton getInstance() {
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
// 1.分配内存
// 2和3的位置可能会被重排序造成颠倒,
//java允许那些在单线程程序内不影响程序运行结果的重排序
// 2.初始化对象
// 3.设置lazyDoubleCheckSingleton指向刚刚分配的内存地址
}
}
}
return lazyDoubleCheckSingleton;
}
}
测试
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread T1 = new Thread(() -> {
LazyDoubleCheckSingleton lazySingleton = LazyDoubleCheckSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":" + lazySingleton);
});
Thread T2 = new Thread(() -> {
LazyDoubleCheckSingleton lazySingleton = LazyDoubleCheckSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":" + lazySingleton);
});
ExecutorService service = Executors.newFixedThreadPool(5);
service.submit(T1);
service.submit(T2);
Thread.sleep(100);
service.shutdownNow();
}
}
结果
lazyDoubleCheckSingleton为Null,需要初始化
pool-1-thread-2:com.design.pattern.creational.singleton.LazyDoubleCheckSingleton@8606d85
pool-1-thread-1:com.design.pattern.creational.singleton.LazyDoubleCheckSingleton@8606d85
因为使用双重判断,只有在示例为空的时候,才会去获取锁,避免了锁的重复获取和释放,减小内存消耗,同时保证了数据一致性。
3.基于类初始化的延迟加载
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton(){
}
private static class InnerClass {
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
}
测试
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread T1 = new Thread(() -> {
StaticInnerClassSingleton singleton = StaticInnerClassSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":" + singleton);
});
Thread T2 = new Thread(() -> {
StaticInnerClassSingleton singleton = StaticInnerClassSingleton.getInstance();
System.out.println(Thread.currentThread().getName() + ":" + singleton);
});
ExecutorService service = Executors.newFixedThreadPool(5);
service.submit(T1);
service.submit(T2);
Thread.sleep(100);
service.shutdownNow();
}
}
结果
pool-1-thread-2:com.design.pattern.creational.singleton.StaticInnerClassSingleton@11438314
pool-1-thread-1:com.design.pattern.creational.singleton.StaticInnerClassSingleton@11438314
当多线程去获取类的初始化锁的时候,只有一个线程可以获得,当一个线程获取到锁后,执行类初始化,对于静态内部类来讲,即便存在重排序,对其他线程也是不可见的,
饿汉式单例
public class HungrySingleton {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
写法简单,类加载的时候就完成了初始化,避免了线程同步问题。
但也因为在类加载的时候完成了初始化,没有延迟加载的效果,如果这个类从始至终都没有被调用过,就造成了内存的浪费。