单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
1.1 饿汉式单例
/**
* 饿汉式单例
*
* @author a_bo
* @version 创建时间:2020年4月18日 下午4:09:42
*/
public class HungrySingleton {
private static final HungrySingleton SINGLETON = new HungrySingleton();
public static HungrySingleton getInstance() {
return SINGLETON;
}
private HungrySingleton() {
}
public static void main(String[] args) {
HungrySingleton s1 = HungrySingleton.getInstance();
System.out.println("s1" + s1);
}
}
1.2 饿汉式静态代码块单例
/**
* 饿汉式静态代码块单例
*
* @author a_bo
* @version 创建时间:2020年4月18日 下午4:15:18
*/
public class HungryStaticSingleton {
private static final HungryStaticSingleton SINGLETON;
static {
SINGLETON = new HungryStaticSingleton();
}
public static HungryStaticSingleton getInstance() {
return SINGLETON;
}
private HungryStaticSingleton() {
}
public static void main(String[] args) {
HungryStaticSingleton s1 = HungryStaticSingleton.getInstance();
System.out.println(s1);
}
}
特点
1.饿汉式单例, 使用static修饰全局唯一, 在类加载的时候就会初始化, 线程安全
2.如果有大批量的饿汉式单例, 系统初始化时会占用大量内存
3.1 懒汉式单例
/**
* 懒汉式单例
*
* @author a_bo
* @version 创建时间:2020年4月18日 下午5:10:46
*/
public class LazySingleton {
private static LazySingleton singleton;
private LazySingleton() {
}
private static LazySingleton getInstance() {
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
public static void main(String[] args) {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}
}
特点
1.使用懒加载, 使用的时候才会去创建, 节省了初始化的内存空间
2.在多线程的场景下会有几率创建两个不一样的单例对象, singleton == null 线程不安全
3.2 饿汉式同步锁单例
/**
* 懒汉式单例
*
* @author a_bo
* @version 创建时间:2020年4月18日 下午5:10:46
*/
public class LazySingleton {
private static LazySingleton singleton;
private LazySingleton() {
}
private static synchronized LazySingleton getInstance() {
if (singleton == null) {
singleton = new LazySingleton();
}
return singleton;
}
public static void main(String[] args) {
LazySingleton instance = LazySingleton.getInstance();
System.out.println(instance);
}
}
特点
1.可以解决线程安全问题, 但是在多线程的场景下, 第一个线程进入同步方法后其余的线程会进行阻塞
/**
* 双重检验锁单例
*
* @author a_bo
* @version 创建时间:2020年4月18日 下午5:33:55
*/
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton singleton;
private DoubleCheckSingleton() {
}
public static DoubleCheckSingleton getInstance() {
if (singleton == null) {
synchronized (DoubleCheckSingleton.class) {
if (singleton == null) {
singleton = new DoubleCheckSingleton();
}
}
}
return singleton;
}
}
特点
1.相对于同步锁的单例, 双重检验锁的粒度更小, 而且DoubleCheckSingleton初始化后不会再进行
同步的操作
2.当多线程执行调用getInstance, 假设A线程先获取到锁, B线程阻塞, 在A线程初始化后通过volatile
关键字将singleton 对象刷新到主内存, 保证了可见性, 然后释放锁, 此时B获取到锁, 判断 singleton
不为空, 就可以返回有值的对象
/**
* 反射破坏单例创建多个实例
*
* @author a_bo
* @version 创建时间:2020年4月18日 下午5:57:26
*/
public class ReflectSingletonTest {
public static void main(String[] args) throws Exception {
// 通过静态方法获取单例
DoubleCheckSingleton s1 = DoubleCheckSingleton.getInstance();
// 通过反射用构造方法创建对象
Class<?> clazz = DoubleCheckSingleton.class;
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
DoubleCheckSingleton s2 = (DoubleCheckSingleton) constructor.newInstance();
DoubleCheckSingleton s3 = (DoubleCheckSingleton) constructor.newInstance();
System.out.println(s2 == s3);//false
System.out.println(s1 == s2);//false
System.out.println(s1 == s3);//false
}
}
特点
1.通过反射用构造方法创建出多个实例对象, 并且每个对象地址值不一致
解决办法
可以对无参构造进行判断当前类的实例是否被创建
private volatile static DoubleCheckSingleton singleton;
private DoubleCheckSingleton() {
if (singleton != null) {
throw new RuntimeException("不允许创建对象");
}
}
/**
* 静态内部类单例
*
* @author a_bo
* @version 创建时间:2020年4月18日 下午6:07:14
*/
public class LazyInnerSingleton {
private LazyInnerSingleton() {
if (LazySingletoncreator.SINGLETON != null) {
throw new RuntimeException("不允许创建对象");
}
}
public static final LazyInnerSingleton getInstance() {
return LazySingletoncreator.SINGLETON;
}
private static class LazySingletoncreator {
private static final LazyInnerSingleton SINGLETON = new LazyInnerSingleton();
}
}
特点
1.内部类默认是不会被加载, 当使用时候才会被加载, SINGLETON 常量全局唯一只会创建一份, 保证了
单例
1.将LazyInnerSingleton 实现序列化接口
2.通过序列化再反序列化创建对象
/**
* 序列化破坏单例测试
*
* @author a_bo
* @version 创建时间:2020年4月18日 下午6:25:59
*/
public class SerializableSingletonTest {
public static void main(String[] args) throws Exception {
LazyInnerSingleton s1 = LazyInnerSingleton.getInstance();
// 创建输出流写入
FileOutputStream os = new FileOutputStream("LazyInnerSingleton.java");
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(s1);
oos.close();
// 创建输入流读
FileInputStream is = new FileInputStream("LazyInnerSingleton.java");
ObjectInputStream ois = new ObjectInputStream(is);
LazyInnerSingleton s2 = (LazyInnerSingleton) ois.readObject();
oos.close();
System.out.println(s1 == s2);//false
}
}
特点
1.通过序列化再发序列化会创建一个新对象
** 解决办法**
1.在序列化的类中增加readResolve方法, ObjectInputStream 在底层会反射调用readResolve
感兴趣的可以看源码更一下
/**
* 静态内部类单例
*
* @author a_bo
* @version 创建时间:2020年4月18日 下午6:07:14
*/
public class LazyInnerSingleton implements Serializable {
private static final long serialVersionUID = 1530724903774028685L;
/** 反序列化的时候返回的对象 */
public Object readResolve() {
return LazySingletoncreator.SINGLETON;
}
private LazyInnerSingleton() {
if (LazySingletoncreator.SINGLETON != null) {
throw new RuntimeException("不允许创建对象");
}
}
public static final LazyInnerSingleton getInstance() {
return LazySingletoncreator.SINGLETON;
}
private static class LazySingletoncreator {
private static final LazyInnerSingleton SINGLETON = new LazyInnerSingleton();
}
}
/**
* 枚举单例
* @author a_bo
* @date 2020年4月18日 下午9:53:03
*/
public enum EnumSingleton {
INSTANCE;
private Object obj;
public Object getData(){
return obj;
}
public void setData(Object data){
obj = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
EnumSingleton i = EnumSingleton.INSTANCE;
i.setData(new Object());
Object o1 = i.getData();
System.out.println(o1);
}
}
特点
1.枚举只能被类加载器加载一次,所以枚举可以保证唯一
2.枚举不能被反射,反射会抛出异常
3.枚举在反序列化时会根据类名和类对象找到唯一的一个枚举
/**
* 容器单例
* @author a_bo
* @date 2020年4月18日 下午10:40:27
*/
public class ContainerSingleton {
private static Map<String, Object> containerMap = new ConcurrentHashMap<String,Object>();
public static <T> T getInstance(Class<? extends T> clazz) throws Exception{
String simpleName = clazz.getSimpleName();
if (!containerMap.containsKey(simpleName)) {
T t = clazz.newInstance();
containerMap.put(simpleName, t);
return t;
}
return (T) containerMap.get(simpleName);
}
}
总结
1.线程不安全
2.适用创建大量对象
/**
* 线程单例
* @author a_bo
* @date 2020年4月18日 下午10:57:32
*/
public class ThreadLocalSingleton {
private static final ThreadLocal<APP> instances = new ThreadLocal<APP>(){
@Override
protected APP initialValue() {
return new APP();
}
};
private ThreadLocalSingleton(){
}
public static APP getInstance(){
return instances.get();
}
public static void main(String[] args) {
APP a1 = ThreadLocalSingleton.getInstance();
APP a2 = ThreadLocalSingleton.getInstance();
System.out.println(a1==a2);//true
}
}
特点
1.只能保证在同一个线程下获取到的时单例的对象
使用单例模式需要考虑一下几点
1.线程安全
2.延迟加载
3.私有化构造
4.防止反射破坏单例
5.防止反序列化破坏单例
6.针对业务场景选择最佳的单例模式