- 简介
- 实现
简介
单例模式(Singleton Pattern是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
介绍
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点:
没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
加粗样式:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
实现我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。
SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo 类使用 SingleObject 类来获取 SingleObject 对象。
核心作用:保证一个类只有一个实例,并且提供一个访问该实例的 全局访问点
饿汉式单例模式:
// 饿汉式单例
public class Hungry {
// 可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
// 单例模式核心思想:构造器私有
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
DCL懒汉式单例模式:
注意:synchronized 解决并发问题,但是因为lazyMan = new LazyMan();不是原子性操作(可以分割,见代码注释),可能发生指令重排序的问题,通过volatil来解决
-
Java 语言提供了 volatile和 synchronized 两个关键字来保证线程之间操作的有序性,volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。
-
原子性就是指该操作是不可再分的。不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。比如 a = 1;
// 懒汉式单例
public class LazyMan {
private LazyMan() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static LazyMan lazyMan; // volatile 为了避免指令重排
// 双重检测锁模式的懒汉式单例 DCL 懒汉式
}
}
}
return lazyMan;
}
//
// public static LazyMan getInstance() {
// if (lazyMan == null) {
// lazyMan = new LazyMan();
// }
// return lazyMan;
// }
// 单线程下确实单例ok,但是多线程并发
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
LazyMan.getInstance();
}).start();
}
}
}
静态内部类
//静态内部类
public class Holder {
//构造器私有
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
反射破坏单例模式
// 懒汉式单例
// 道高一尺,魔高一丈
public class LazyMan {
private static boolean qinjiang = false;
private LazyMan() {
if(qinjiang == false){
qinjiang=true;
}else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
// synchronized (LazyMan.class){
// if (lazyMan!=null){
// throw new RuntimeException("不要试图使用反射破坏异常");
// }
// }
// System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static LazyMan lazyMan; // volatile 为了避免指令重排
// 双重检测锁模式的懒汉式单例 DCL 懒汉式
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();// 不是一个原子性操作
}
}
}
return lazyMan;
}
// 单线程下确实单例ok,但是多线程并发
public static void main(String[] args) throws Exception {
// 反射 可以破环这种单例
// LazyMan instance = LazyMan.getInstance();
Field qinjiang = LazyMan.class.getDeclaredField("qinjiang");
qinjiang.setAccessible(true);
Constructor declaredConstructor =LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有构造器
LazyMan instance=declaredConstructor.newInstance();
qinjiang.set(instance,false);
LazyMan instance2=declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
枚举
枚举:通过反射破解枚举发现不成功:
1、普通的反编译会欺骗开发者,说enum枚举是无参构造
2、实际enum为有参构造(见后面);
3、通过反射破解枚举会发现抛出异常
Exception in thread “main” java.lang.IllegalArgumentException: Cannot reflectively create enum objects at java.lang.reflect.Constructor.newInstance(Constructor.java:417) at com.ph.single.Test.main(EnumSingle.java:19)
// enum 本身也是一个 class 类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
//Constructor declaredConstructor=EnumSingle.class.getDeclaredConstructor(null);
// 枚举没有无参构造,只有有参构造
Constructor declaredConstructor=EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
// java.lang.NoSuchMethodException: com.kuang.single.EnumSingle. 没有空参的构造方法
// java.lang.IllegalArgumentException: Cannot reflectively create enum objects 反射不能破坏枚举的单例
System.out.println(instance1);
System.out.println(instance2);
}
}
结论:枚举没有无参构造,只有有参构造



