设计模式——单例模式
基本概念
-
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
-
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
-
注意:
-
1、单例类只能有一个实例。
-
2、单例类必须自己创建自己的唯一实例。
-
3、单例类必须给所有其他对象提供这一实例。
-
-
优点:
-
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
-
2、避免对资源的多重占用(比如写文件操作)。
-
-
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
-
使用场景:
-
1、要求生产唯一序列号。
-
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
-
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
-
-
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
几种单例模式实现
-
饿汉式
-
单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class Hungry { private Hungry(){ //构造函数私有 } private static Hungry hungry = new Hungry();//饿汉式直接在本类中实例化对象 public static Hungry getInstance(){ return hungry; } //反射破解单例模式 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Hungry instance1 = Hungry.getInstance(); Constructor<Hungry> declaredConstructor = Hungry.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); Hungry instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
-
-
懒汉式
-
线程不安全
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
-
线程安全(同步方法)
public class LazyManDemo { private LazyManDemo(){ System.out.println(Thread.currentThread().getName()); } private static LazyManDemo instance; public static synchronized LazyManDemo getInstance(){ if(instance==null){ instance = new LazyManDemo(); } return instance; } //检查线程安全 public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazyManDemo.getInstance(); }).start(); } }
-
线程安全(双检锁)DCL懒汉式
-
instance=new LazyMan();并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
-
给 instance 分配内存
-
调用 Singleton 的构造函数来初始化成员变量
-
将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
-
-
三件事情的执行顺序不一定是预期中的,所以 instance 变量声明成 volatile 防止指令重排
-
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class LazyMan { private LazyMan(){ System.out.println(Thread.currentThread().getName()); } private volatile static LazyMan instance; public static LazyMan getInstance(){ if(instance==null){ synchronized (LazyMan.class){ if(instance==null){ instance = new LazyMan(); } } } return instance; } //反射暴力破解 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { LazyMan instance1 = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = (Constructor<LazyMan>) instance1.getClass().getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan lazyMan1 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(lazyMan1); } }
-
-
静态内部类
-
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
public class Singleton { private Singleton (){ System.out.println(Thread.currentThread().getName()); }; private static class Inner{ private static final Singleton INSTANCE = new Singleton(); } public static final Singleton getInstance(){ return Inner.INSTANCE; } //检查线程安全 public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ Singleton.getInstance(); }).start(); } } }
-
-
枚举
-
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
public enum EnumSingle{ INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } //实际是带有两个参数的构造函数,难以用反射破解
-

浙公网安备 33010602011771号