单例模式(Singleton Pattern):保证一个类仅有一个对象,并提供一个访问它的全局访问点。(Ensure a class only has one instance,and provide a globe point of access to it.)
常见应用场景:
单例模式的优点:
常见单例模式有以下7种:
1.饿汉式:先创建后使用,线程安全,占用内存。代码如下:
/**
* 饿汉式单例模式
* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public class ClassA {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassA(){ }
//2.在类的内部创建一个类的实例
//类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
private static final ClassA instance = new ClassA();
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
//方法没有同步,调用效率高!
public static ClassA getInstance(){
return instance;
}
//测试
public static void main(String[] args) {
ClassA a = ClassA.getInstance();
ClassA b = ClassA.getInstance();
System.out.println(a==b);
}
}
控制台输出的结果如下图:
2.懒汉式:用的时候才创建,线程不安全,加锁会影响效率。资源利用率高了,但是,每次调用getInstance()方法都要同步,并发效率较低。代码如下:
/**
* 懒汉式单例模式
* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public class ClassB {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassB(){ }
//2.在类的内部创建一个类的实例
private static ClassB instance ;
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static synchronized ClassB getInstance(){
if(instance == null) {
instance = new ClassB();
}
return instance;
}
//测试
public static void main(String[] args) {
ClassB a = ClassB.getInstance();
ClassB b = ClassB.getInstance();
System.out.println(a==b);
}
}
控制台输出的结果如下图:
3.静态内部类方式:也即饿汉式和懒汉式的组合,调用getInstance()方法时才创建,达到了类似懒汉式的效果,同时又是线程安全的。代码如下:
/**
* 使用静态内部类方式实现单例模式
* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public class ClassC {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassC(){ }
//2.在类的内部创建一个类的实例
private static class Holder{
private static ClassC instance = new ClassC();
}
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassC getInstance(){
return Holder.instance;
}
//测试
public static void main(String[] args) {
ClassC a = ClassC.getInstance();
ClassC b = ClassC.getInstance();
System.out.println(a==b);
}
}
控制台输出的结果如下图:
4.枚举方法:线程安全,实现简单,调用效率高,不能延时加载。枚举本身就是单例模式,由JVM从根本上提供保障并且可以天然的防止反射和反序列化漏洞!需要继承的场景它就不适用了。枚举方式是Effective Java作者提倡的方式。代码如下:
/**
* 使用枚举方法实现单例模式
* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public enum ClassD {
//定义一个枚举的元素,它就代表了Singleton的一个实例。
INSTANCE;
//对外部提供调用方法:将创建的对象返回,只能通过类来调用
public void otherMethod(){
//功能处理
}
//测试
public static void main(String[] args) {
ClassD a = ClassD.INSTANCE;
ClassD b = ClassD.INSTANCE;
System.out.println(a==b);
}
}
5.双重校验锁式:通常线程安全,加volatile的作用是禁止指令重排。(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)代码如下:
/**
* 使用双重校验锁来实现单例模式
* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public class ClassE {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassE(){ }
//2.在类的内部创建一个类的实例
private volatile static ClassE instance; //volatile作用:保证多线程可以正确处理instance
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassE getInstance(){
if(instance == null){ //检查实例,如果为空,就进入同步代码块
synchronized (ClassE.class){
if(instance == null){ //再检查一次,仍未空才创建实例
instance = new ClassE();
}
}
}
return instance;
}
//测试
public static void main(String[] args) {
ClassE a = ClassE.getInstance();
ClassE b = ClassE.getInstance();
System.out.println(a==b);
}
}
6.使用ThreadLocal实现:线程安全,ThreadLocal采用以空间换时间的方式,为每一个线程都提供一份变量,因此可以同时访问而互不影响。代码如下:
/**
* 使用ThreadLocal实现单例模式
* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public class ClassF {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassF(){ }
//2.在类的内部创建一个类的实例
private static final ThreadLocal<ClassF> tls = new ThreadLocal<ClassF>(){
@Override
protected ClassF initialValue(){
return new ClassF();
}
};
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static ClassF getInstance(){
return tls.get();
}
//测试
public static void main(String[] args) {
ClassF a = ClassF.getInstance();
ClassF b = ClassF.getInstance();
System.out.println(a==b);
}
}
控制台输出的结果如下图:
7.使用CAS锁来实现:(CAS锁(Compare and Swap):比较并交换,是一种有名的无锁算法,属于乐观锁)。用CAS锁来实现单例模式是线程安全的,代码如下:
/**
* 使用CAS锁来实现单例模式
* @author cui_yonghua https://blog.csdn.net/cui_yonghua/article/details/90512943
*/
public class ClassG {
//1.私有化构造方法,使得在类的外部不能调用此方法,限制产生多个对象
private ClassG(){ }
//2.在类的内部创建一个类的实例
private static final AtomicReference<ClassG> instance = new AtomicReference<ClassG>();
//3.对外部提供调用方法:将创建的对象返回,只能通过类来调用
public static final ClassG getInstance(){
for(;;){
ClassG current = instance.get();
if(current != null){
return current;
}
current = new ClassG();
if(instance.compareAndSet(null,current)){
return current;
}
}
}
//测试
public static void main(String[] args) {
ClassG a = ClassG.getInstance();
ClassG b = ClassG.getInstance();
System.out.println(a==b);
}
}
控制台输出的结果如下图:
如果如果想了解更多设计模式,可点击:设计模式概述 以及 23种设计模式的介绍
如果觉得文章写的不错,也可以小小地打赏一下嘛~ 也期待合作,“码”上改变~