单例模式
1、概念
(1)什么是单例模式?
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例,并提供一个访问它的全局访问点。即一个类只有一个对象实例。
适用性:当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
(2)单例模式实现原理
(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
(2)在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。
(3)定义一个静态方法返回这个唯一对象。
2、单例模式的实现方式
(1):饿汉式
就是使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故又被称为“饿汉模式”)。常见的实现办法就是直接new实例化。
package com.example.designmode.singleton;
/**
* 饿汉式 :就是使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故又被称为“饿汉模式”),
* 常见的实现办法就是直接new实例化。
* 优点:实现起来简单,没有多线程同步问题。
* 缺点:当类SingletonTest被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,
* 从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),
* 当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。
*/
public class EagerSingleton {
// jvm保证在任何线程访问eagerSingleton静态变量之前一定先创建了此实例
private static EagerSingleton eagerSingleton = new EagerSingleton();
// 私有的默认构造子,保证外界无法直接实例化
private EagerSingleton() {
}
// 提供全局访问点获取唯一的实例
public static EagerSingleton getInstance() {
return eagerSingleton;
}
}
总结:
优点:实现起来简单,没有多线程同步问题。
缺点:当类SingletonTest被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。
(2):懒汉式
延迟加载(懒汉式)就是调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。不着急,故又称为“懒汉模式”)。常见的实现方法就是在get方法中进行new实例化。
package com.example.designmode.singleton;
/**
* 延迟加载(懒汉式)就是调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。不着急,故又称为“懒汉模式”),
* 常见的实现方法就是在get方法中进行new实例化。
*
*/
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
总结:
优点:实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。
缺点:在多线程环境中,这种实现方法是完全错误的,根本不能保证单例的状态。
(3):线程安全的“懒汉模式”
package com.example.designmode.singleton;
/**
* 线程安全的“懒汉模式”
*
*/
public class SynchronizedLazySingleton {
private static SynchronizedLazySingleton lazySingleton = null;
private SynchronizedLazySingleton() {
}
public static synchronized SynchronizedLazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new SynchronizedLazySingleton();
}
return lazySingleton;
}
}
总结:
优点:在多线程情形下,保证了“懒汉模式”的线程安全。
缺点:众所周知在多线程情形下,synchronized方法通常效率低,显然这不是最佳的实现方案。
(4):DCL双检查锁机制(DCL:double checked locking)
算是单例模式的最佳实现方式之一。内存占用率高,效率高,线程安全,多线程操作原子性。
package com.example.designmode.singleton;
/**
* DCL双检查锁机制(DCL:double checked locking)
*
*/
public class DoubleCheckedLockingLazySingleton {
private static DoubleCheckedLockingLazySingleton lazySingleton = null;
private DoubleCheckedLockingLazySingleton() {
}
public static DoubleCheckedLockingLazySingleton getInstance() {
// 第一次检查lazySingleton是否被实例化出来,如果没有进入if块
if (lazySingleton == null) {
synchronized (DoubleCheckedLockingLazySingleton.class) {
// 某个线程取得了类锁,实例化对象前第二次检查lazySingleton是否已经被实例化出来,如果没有,才最终实例出对象
if (lazySingleton == null) {
lazySingleton = new DoubleCheckedLockingLazySingleton();
}
}
}
return lazySingleton;
}
}
(5):静态内部类懒汉式
原理:类装载时,静态内部类不会被装载。调用公共方法时,加载内部类,加载只会有一次,所以线程安全。(在类进行初始化时,别的线程是无法进入的,提供了安全性保证)
package com.example.designmode.singleton;
/**
* 静态内部类懒汉式:当任何一个线程第一次调用getInstance时,都会使StaticInnerClasssLazySingletonInner被加载和被初始化,此时
* 静态初始化器将执行lazySingleton的初始化操作(被调用时才初始化)。初始化静态数据时,java提供了线程安全性保证,所以不需要任何同步。
*
* 注:类装载时,静态内部类不会被装载。调用公共方法时,加载内部类,加载只会有一次,所以线程安全。【在类进行初始化时,别的线程是无法进入的。 】
*/
public class StaticInnerClasssLazySingleton {
private StaticInnerClasssLazySingleton() {
}
private static class StaticInnerClasssLazySingletonInner {
// 利用JVM里静态成员只被加载一次的特点来保证只有一个实例对象
private static final StaticInnerClasssLazySingleton lazySingleton = new StaticInnerClasssLazySingleton();
}
public static StaticInnerClasssLazySingleton getInstance() {
return StaticInnerClasssLazySingletonInner.lazySingleton;
}
}
总结:
优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高。
缺点:暂无。
(6):枚举式
保证单例的方式: 首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。也就是说,因为Enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。
package com.example.designmode.singleton;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public enum EnumSingleton {
INSTANCE;
public void test() {
log.info("测试枚举单例类的用法");
}
public static void main(String[] args) {
System.out.println(String.valueOf(EnumSingleton.INSTANCE.hashCode()));
System.out.println(String.valueOf(EnumSingleton.INSTANCE.hashCode()));
System.out.println(String.valueOf(EnumSingleton.INSTANCE.hashCode()));
System.out.println(String.valueOf(EnumSingleton.INSTANCE.hashCode()));
System.out.println(String.valueOf(EnumSingleton.INSTANCE.hashCode()));
EnumSingleton.INSTANCE.test();
}
}
优点:简单、防止多次实例化,可以防止序列化破坏和反射攻击、默认就是线程安全的。
借用 《Effective Java》一书中的话: 单元素的枚举类型已经成为实现Singleton的最佳方法。
3、总结
枚举式式最简单最优秀的单例写法,可以防止反射工具(详细参考《如何防止单例模式被JAVA反射攻击》)和序列化破坏(详细参考《JAVA序列化 》)。建议编程时采用静态内部类懒汉式(不能防止反射和序列化破坏),当然写成枚举式就更好啦。
参考博客:
https://www.cnblogs.com/binaway/p/8889184.html
https://blog.csdn.net/u013256816/article/details/50966882