单例模式
描述:
单例模式是系统中单例的类只有一个实例化对象,系统中的所有对该类的调用和访问都通过该对象来进行
常用场景:
当一个系统中需要注意实例的个数,减少实例的频繁创建和销毁造成的资源浪费。
比如系统中的字典类就可以用单例模式来实现
项目结构:
实现:
1.懒汉式(最简单最基本的实现方法)
这种是单例模式实现最简单的方式,但是在多线程下会有并发问题。
/**
* @author Carl
* @version 1.0
* @date 2020/8/10 14:29
* @description 实现方式--懒汉式:这种是单例模式实现最简单的方式,但是在多线程下会有并发问题
**/
public class MySingleton1 {
private MySingleton1(){
}
private static MySingleton1 mySingleton1;
public static MySingleton1 getInstance(){
if(mySingleton1 == null){
mySingleton1 = new MySingleton1();
}
return mySingleton1;
}
public void doSomething(){
System.out.println("get a singleton");
}
}
2.懒汉式-加synchronize锁
这种实现模式线程安全,并且在真正用到它的地方才会进行实例化,但是由于锁的竞争,对性能方面有一定影响。
/**
* @author Carl
* @version 1.0
* @date 2020/8/10 14:31
* @description 实现方式--懒汉式--线程安全:这种实现模式线程安全,并且在真正用到它的地方才会进行实例化,但是由于锁的竞争,对性能方面有一定影响
**/
public class MySingleton2 {
private MySingleton2(){}
private static MySingleton2 mySingleton2;
public static synchronized MySingleton2 getInstance(){
if(mySingleton2 == null){
mySingleton2 = new MySingleton2();
}
return mySingleton2;
}
public void doSomething(){
System.out.println("get a singleton");
}
}
3.饿汉式
这种单例模式通过类的加载来实现,由于没有加锁,因此在效率上没有太大的影响,但是如果在项目运行过程中有其他的方式加载类,这时就没有达到lazy loading的效果,也就是在实际第一次要使用到时才创建。
/**
* @author Carl
* @version 1.0
* @date 2020/8/10 14:44
* @description 实现方式--饿汉式:这种单例模式通过类的加载来实现,由于没有加锁,因此在效率上没有太大的影响,但是如果在项目运行过程中有其他的方式加载类,这时就没有达到lazy loading的效果
**/
public class MySingleton3 {
private MySingleton3(){}
private static MySingleton3 mySIngleton3 = new MySingleton3();
public static MySingleton3 getInstance(){
return mySIngleton3;
}
public void doSomething(){
System.out.println("get a singleton");
}
}
4.静态内部类实现(懒汉式)
该方法通过静态内部类的方式实现懒加载,类初始化的时候MySingletonHandler并没有加载,只有在调用getinstance方法的时候才会通过 MySingletonHandler进行MySingleton实例的加载。由于只在真正调用的时候通过加载创建一个实例,因此也是线程安全的。但是静态内部类无法接受外部传参,这也是这种实现方式最大的缺陷。
/**
* @author Carl
* @version 1.0
* @date 2020/8/4 16:43
* @description 实现方式--静态内部类:该方法通过静态内部类的方式实现懒加载,类初始化的时候MySingletonHandler并没有加载,只有在调用getinstance方法的时候才会通过
* * MySingletonHandler进行MySingleton实例的加载。由于只在真正调用的时候通过加载创建一个实例,因此也是线程安全的。但是静态内部类无法接受外部传参,这
* * 也是这种实现方式最大的缺陷
**/
public class MySingleton {
private static class MySinletonHandler{
private static MySingleton mySingleton = new MySingleton();
public static MySingleton getMySingleton(){
return mySingleton;
}
}
private MySingleton(){
}
public static MySingleton getInstance(){
return MySinletonHandler.getMySingleton();
}
public void doSomething(){
System.out.println("get a singleton");
}
}
5.双重校验锁
这种单例的实现方式是通过双重检验锁进行同步控制的,当两个线程都运行到if(mySingleton4 == null) 的时候,线程1获得允许进入到synchronized代码块中后再次判断mySingleton4==null不等于null的时候创建一个新的实例,并退出同步代码块,然后线程2进入同步代码快,由于mySingleton4不为空,线程2直接退出同步代码块。这种方式在理论上可以实现线程安全,但是jvm的内存模型结构中的无序写入会导致 创建对象 时一段时间内mySingleton4引入了一个并没有被完全初始化的实例,线程2调用getInstance()的时候由于mySingleton4引用了一个不为空的但是初始化不完整的实例,导致单例模式的实现失败。尽管在jdk1.5之后 java在内存模型上做了很大的改善,增加了volatile关键字控制顺序执行,但是该实现方式却显得有点繁琐了。并且双重检验锁的实现方式可以被反射暴力破解。目前企业中大部分单例模式都是用这种方法实现的,只要不去主动的使用反射创建实例,一般不会出现什么问题。
/**
* @author Carl
* @version 1.0
* @date 2020/8/10 16:34
* @description 实现方式--双重校验锁:这种单例的实现方式是通过双重检验锁进行同步控制的,当两个线程都运行到if(mySingleton4 == null) 的时候,线程1获得允许进入到synchronized代码块中
* 后再次判断mySingleton4==null不等于null的时候创建一个新的实例,并退出同步代码块,然后线程2进入同步代码快,由于mySingleton4不为空,线程2直接退
* 出同步代码块。这种方式在理论上可以实现线程安全,但是jvm的内存模型结构中的无序写入会导致 创建对象 时一段时间内mySingleton4引入了一个并没有被完全
* 初始化的实例,线程2调用getInstance()的时候由于mySingleton4引用了一个不为空的但是初始化不完整的实例,导致单例模式的实现失败。尽管在jdk1.5之后
* java在内存模型上做了很大的改善,增加了volatile关键字控制顺序执行,但是该实现方式却显得有点繁琐了。并且双重检验锁的实现方式可以被反射暴力破解。目
* 前企业中大部分单例模式都是用这种方法实现的,只要不去主动的使用反射创建实例,一般不会出现什么问题。
**/
public class MySingleton4 {
private static MySingleton4 mySingleton4;
//jdk1.5之后的写法
//private static volatile MySingleton4 mySingleton4;
public static MySingleton4 getInstance(){
if(mySingleton4 == null){
synchronized (MySingleton4.class){
if(mySingleton4 == null){
mySingleton4 = new MySingleton4();//创建对象
}
}
}
return mySingleton4;
}
public void doSomething(){
System.out.println("get a singleton");
}
}
6.枚举实现
最后一种实现单例的方式目前也是最受信赖的实现方式。通过枚举类反编译之后其实是一个final class,枚举类的构造器默认就是private的,枚举类序列化的时候只输出name,反序列化的时候通过valueOf()方法查找,因此并不是传统意义上的序列化,也就不存在序列化问题。但是这种枚举的实现方式也是饿汉式的,在类初始化的时候就已经给实例分配了内存。
/**
* @author Carl
* @version 1.0
* @date 2020/8/10 16:48
* @description 实现方式--枚举:最后一种实现单例的方式目前也是最受信赖的实现方式。通过枚举类反编译之后其实是一个final class,枚举类的构造器默认就是private的,枚举类序列化
* 的时候只输出name,反序列化的时候通过valueOf()方法查找,因此并不是传统意义上的序列化,也就不存在序列化问题。但是这种枚举的实现方式也是饿汉式
* 的,在类初始化的时候就已经给实例分配了内存。
**/
public enum MySingleton5 {
INSTANCE;
public void doSomething(){
System.out.println("get a singleton");
}
}
7.测试
import java.lang.reflect.Constructor;
/**
* @author Carl
* @version 1.0
* @date 2020/8/4 16:49
* @description 六种单例模式测试
**/
public class Domain {
public static void main(String[] args) {
//静态内部类
MySingleton mySingleton = MySingleton.getInstance();
mySingleton.doSomething();
//懒汉式,线程不安全
MySingleton1 mySingleton1 = MySingleton1.getInstance();
mySingleton1.doSomething();
//懒汉式,线程安全
MySingleton2 mySingleton2 = MySingleton2.getInstance();
mySingleton2.doSomething();
//饿汉式
MySingleton3 mySingleton3 = MySingleton3.getInstance();
mySingleton3.doSomething();
//双重校验锁
MySingleton4 mySingleton4 = MySingleton4.getInstance();
mySingleton4.doSomething();
//枚举
MySingleton5.INSTANCE.doSomething();
//使用反射暴力破解双重检验锁实现的单例模式
try {
Class<MySingleton4> clazz = (Class<MySingleton4>)Class.forName("com.yzj.dobetter.designPattern.singletonPattern.MySingleton4");
Constructor<MySingleton4> c = clazz.getDeclaredConstructor();
c.setAccessible(true);
MySingleton4 t = (MySingleton4) c.newInstance(null);
System.out.println("两个实例地址是否相等:"+ (t==MySingleton4.getInstance()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
8.测试结果
get a singleton
get a singleton
get a singleton
get a singleton
get a singleton
get a singleton
两个实例地址是否相等:false
说明
实现中均以做出具体说明