图书馆网站建设网站的目的,wordpress主题切换不了,图床网站怎么做,学院网站设计说明书1 单例模式介绍单例模式#xff08;Singleton Pattern#xff09;是Java中最为基础的设计模式。这种类型的设计模式属于创建型模式#xff0c;它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类#xff0c;该类负责创建自己的对象#xff0c;同时确保只有单个对…1 单例模式介绍单例模式Singleton Pattern是Java中最为基础的设计模式。这种类型的设计模式属于创建型模式它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类该类负责创建自己的对象同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式可以直接访问不需要实例化该类的对象。1.1 场景目的控制实例数目节省系统资源适用若一个全局使用的类频繁地创建与销毁单例模式可以证一个类仅有一个实例并提供一个访问它的全局访问点。要求生产唯一序列号WEB 中的计数器不用每次刷新都在数据库里加一次用单例先缓存起来创建的一个对象需要消耗的资源过多比如 I/O 与数据库的连接等。优点如此内存中只有一个实例减少了内存的开销避免对资源的多重占用比如写文件操作。缺点没有接口不能继承与单一职责原则冲突一个类应该只关心内部逻辑而不关心外面怎么样来实例化。注spring boot项目中将该对象注入到bean那么该对象就默认为单例这时也可以使用Scope去设定单例和原型以及其他模式。1.2基础实现方式以下是基础的实现方式创建一个 Singleton 类存在私有构造函数和本身的一个静态实例。Singleton 类提供了一个静态方法供外界获取它的静态实例。Client我们的演示类使用 Singleton 类来获取 Singleton 对象。 1.创建Singleton单例类public class Singleton {//让构造函数为 private这样该类就不会被实例化private Singleton(){}//创建 SingleObject 的一个对象private static Singleton INSTANCE new Singleton();//获取唯一可用的对象public static Singleton getInstance(){return INSTANCE;}} 2.从singleton类获取唯一的对象。public class Client {public static void main(String[] args) {//不合法的构造函数--因为SingleObject()私有不可见
// Singleton singleton new Singleton();Singleton singleton1 Singleton.getInstance();Singleton singleton2 Singleton.getInstance();System.out.println(singleton1);System.out.println(singleton2);}
}控制台输出2 实现方式汇总类型Lazy 初始化多线程安全实现难度懒汉式--线程不安全是否容易懒汉式--线程安全是是容易饿汉式否是容易双重校验锁是是较复杂双重校验锁volatile是是较复杂静态内部类是是一般枚举否 是容易ThreadLocal是是较复杂CAS锁是是较复杂2.1 懒汉式--线程不安全这种方式是最基本的实现方式最大的问题是不支持多线程因为没有加锁 synchronized所以严格意义上它并不算单例模式。public class Lazyman1 {private static Lazyman1 instance;private Lazyman1(){}public static Lazyman1 getInstance() {if (instance null){instance new Lazyman1();}return instance;}
}
接下来介绍的几种实现方式都支持多线程但是在性能上有所差异。2.2 懒汉式--线程安全这种方式能够在多线程中很好的工作但是效率低。优点第一次调用才初始化避免内存浪费。缺点必须加锁 synchronized 才能保证单例但加锁会影响效率。public class Lazyman2 {private static Lazyman2 instance;private Lazyman2(){}public static synchronized Lazyman2 getInstance() {if (instance null){instance new Lazyman2();}return instance;}
}2.3 饿汉式这种方式比较常用但容易产生垃圾对象。它基于类加载机制避免了多线程的同步问题因为类加载过程JVM会自动加锁因此保证了单例特性。优点没有加锁执行效率会提高。缺点类加载时就初始化浪费内存。public class Hungry {private Hungry(){}private final static Hungry HUNGRY new Hungry();public static Hungry getInstance(){return HUNGRY;}
}2.4 双重校验锁DCL即 double-checked locking这种方式采用双锁机制安全且在多线程情况下能保持高性能。不过DCL还是可能会出现指令重排的问题~public class DLC {private static DLC instance;private DLC(){}public static DLC getInstance() {if (instancenull){synchronized (LazyMan.class){if (instancenull){instance new DLC();//不是一个原子性操作/** 1.分配内存空间* 2.执行构造方法初始化对象* 3.把这个对象指向这个空间** 可能* 123* 132 若多线程A到达3,B会认为lazyMan非空但是lazyMan此时还没有完成构造那么就会有问题*/}}}return instance;}
}2.5 双重校验锁volatile用于处理DCL可能出现的问题指令重排解决方案只需要给instance的声明加上volatile关键字volatile--静止指令重排对它的写操作就会有一个内存屏障。public class DLCVolatile {private static volatile DLCVolatile instance;private DLCVolatile(){}public static DLCVolatile getInstance() {if (instancenull){synchronized (LazyMan.class){if (instancenull){instance new DLCVolatile();}}}return instance;}
}2.6 静态内部类也称为登记类这种方式能达到双检锁方式一样的功效但实现更简单。对静态域使用延迟初始化应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况双检锁方式可在实例域需要延迟初始化时使用。这种方式也利用了类加载机制保证初始化instance时只有一个线程他和饿汉式不同在于饿汉式只要单例类被加载那么instance就会被实例化而静态内部类类加载后instance不一定被初始化因为InnerClass类没有被主动使用只有显示通过调用getInstance方法时才会显示装载InnerClass类从而实例化instance。//静态内部类
public class Holder {private Holder(){}public static Holder getInstance(){return InnerClass.INSTANCE ;}public static class InnerClass{private static final Holder INSTANCE new Holder();}
}2.7 枚举实现单例模式的最佳方法简洁自动支持序列化机制防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式它不仅能避免多线程同步问题而且还自动支持序列化机制防止反序列化重新创建新的对象绝对防止多次实例化。不过由于 JDK1.5 之后才加入 enum 特性在实际工作中很少用。 实现方式public enum EnumSignle {INSTANCE;public EnumSignle getInstance(){return INSTANCE;}
}class Test{public static void main(String[] args) {EnumSignle enumSignle1 EnumSignle.INSTANCE;EnumSignle enumSignle2 EnumSignle.INSTANCE;System.out.println(enumSignle1);System.out.println(enumSignle2);}
}控制台输出 通过枚举将已有类改造为单例类public class Singleton {private Singleton(){}public static enum EnumSignle1 {INSTANCE;private Singleton instance null;private EnumSignle1(){instance new Singleton();}public Singleton getInstance(){return instance;}}
}class Test1{public static void main(String[] args) {Singleton singleton1 Singleton.EnumSignle1.INSTANCE.getInstance();Singleton singleton2 Singleton.EnumSignle1.INSTANCE.getInstance();System.out.println(singleton1 singleton2);}
}2.8 ThreadLocal--线程安全ThreadLocal会为每一个线程提供一个独立的变量副本从而隔离了多个线程对数据的访问冲突。对于多线程资源共享的问题同步机制采用了“以时间换空间”的方式而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量让不同的线程排队访问而后者为每一个线程都提供了一份变量因此可以同时访问而互不影响。public class TLSingleton {private static final ThreadLocalTLSingleton tlSingleton new ThreadLocalTLSingleton(){Overrideprotected TLSingleton initialValue() {return new TLSingleton();}};private TLSingleton(){}public static TLSingleton getInstance() {return tlSingleton.get();}
}2.9 CAS锁实现线程安全public class CASSingleton {private CASSingleton(){}private static final AtomicReferenceCASSingleton INSTANCE new AtomicReferenceCASSingleton();public static CASSingleton getInstance() {for (; ; ) {CASSingleton current INSTANCE.get();if (current ! null) {return current;}current new CASSingleton();if (INSTANCE.compareAndSet(null, current)) {return current;}}}
} 总结一般情况下不建议使用第1种和第2种懒汉方式建议使用第3种饿汉方式。只有在要明确实现lazy loading效果时才会使用第6种静态内部类方式。如果涉及到反序列化创建对象时可以尝试使用第7种枚举方式。如果有其他特殊的需求可以考虑使用第4种双检锁方式。