单例模式(饿汉式单例、懒汉式单例、DCL模式、静态内部类实现、枚举实现)看这一篇就够了!
作者:互联网
single(二十一种设计模式之一)
1.单例模式要素
确保一个类中只有一个实例,并提供一个全局访问点
a.私有构造方法
b.私有静态引用指向自己实例
c.以自己实例为返回值的公有静态方法
2.饿汉式单例
public class Hungry {
//私有的构造函数,保证外类不能实例化本类
private Hungry (){}
//自己创建一个类的实例化
private final static Hungry HUNGRY= new Hungry ();
//创建一个get方法,返回一个实例HUNGRY
public static Hungry getInstance(){
return HUNGRY;
}
}
2.1饿汉式单例特点
-
饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。
-
从线程安全性上讲,饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。
3.懒汉式单例
public class LazyMan{
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
lazyMan=new LazyMan();
}
return lazyMan;
}
}
不难发现在多线程并发时懒汉式单例并不安全,于是出现
4.懒汉式单例双重检测锁(DCL模式)
public class LazyMan{
private LazyMan(){
}
private static LazyMan lazyMan;
//双重检测锁
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
}
在lazyMan为空时在LazyMan这个类上增加锁,保证这个类只有一个,但有可能在并发极端情况下是会出现问题的
//双重检测锁
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan(); //不是原子性的操作
/**
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向这个空间
我们期望jvm上是执行123的顺序,
但在这里会出现指令重排的现象,
jvm执行顺序为132
假设有a线程,执行顺序为1,2,3,
当执行到2时,b线程开始执行顺序为
1,3,2这时在判断lazyMan时结果不为空
于是执行返回操作但这是的lazyMan对象
是有问题的
*/
}
}
}
return lazyMan;
}
为了避免产生指令重排,增加volatile
public class LazyMan{
private LazyMan(){
}
private volatile static LazyMan lazyMan;
//双重检测锁
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
}
5.懒汉式单例(静态内部类实现)
public class Holder{
private Holder(){
}
public static Holder getInstace(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
但以上这些懒汉式其实都是不安全的,因为反射可以破坏单例模式
public class LazyMan{
private LazyMan(){
}
private volatile static LazyMan lazyMan;
//双重检测锁
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
//反射
public static void main (String[] args){
LazyMan instance1=LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor=LazyMan.class.getDeclaredConstructor(null)
//破坏构造器私有化
declaredConstructor.setAccessible(true);
LazyMan instance2 =declaredConstructor.newInstance();
//打印的值并不相同,说明单例已被破坏
System.out.println(instance1);
System.out.println(instance2);
}
当然这种也有解决的方法
public class LazyMan{
private LazyMan(){
synchronized(LazyMan.class){
if(lazyMan!=null){
throw new RuntimeException("请不要使用反射破坏单例异常")
}
private volatile static LazyMan lazyMan;
//双重检测锁
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
//反射
public static void main (String[] args){
LazyMan instance1=LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor=LazyMan.class.getDeclaredConstructor(null)
//破坏构造器私有化
declaredConstructor.setAccessible(true);
LazyMan instance2 =declaredConstructor.newInstance();
//打印时报错
System.out.println(instance1);
System.out.println(instance2);
}
这时再次执行main方法时会报错运行时异常
如果将两个instance对象都直接new出来
public class LazyMan{
private LazyMan(){
synchronized(LazyMan.class){
if(lazyMan!=null){
throw new RuntimeException("请不要使用反射破坏单例异常")
}
private volatile static LazyMan lazyMan;
//双重检测锁
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
//反射
public static void main (String[] args){
//LazyMan instance1=LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor=LazyMan.class.getDeclaredConstructor(null)
//破坏构造器私有化
declaredConstructor.setAccessible(true);
LazyMan instance1 =declaredConstructor.newInstance();
LazyMan instance2 =declaredConstructor.newInstance();
//打印时打印不同的地址-->单例再次被破坏
System.out.println(instance1);
System.out.println(instance2);
}
我们发现单例依然可以被破坏
此时我们再次进行升级定义一个假设其他人都不知道的变量
public class LazyMan{
//定义参数,也可以说密钥
private static boolean flag=false;
private LazyMan(){
synchronized(LazyMan.class){
if(flag==fasle){
flag=true;
}else{
throw new RuntimeException("请不要使用反射破坏单例异常")
}
}
private volatile static LazyMan lazyMan;
//双重检测锁
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
//反射
public static void main (String[] args){
//LazyMan instance1=LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor=LazyMan.class.getDeclaredConstructor(null)
//破坏构造器私有化
declaredConstructor.setAccessible(true);
LazyMan instance1 =declaredConstructor.newInstance();
LazyMan instance2 =declaredConstructor.newInstance();
//打印时抛出异常
System.out.println(instance1);
System.out.println(instance2);
}
这时我们发现好像单例不会被破坏了,但是如果有人破解了flag字段,使得flag字段私有化失效
public class LazyMan{
//定义参数,也可以说密钥
private static boolean flag=false;
private LazyMan(){
synchronized(LazyMan.class){
if(flag==fasle){
flag=true;
}else{
throw new RuntimeException("请不要使用反射破坏单例异常")
}
}
private volatile static LazyMan lazyMan;
//双重检测锁
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
//反射
public static void main (String[] args){
//LazyMan instance1=LazyMan.getInstance();
Field flag =LazyMan.class.getDeclaredField("flag")
//使得私有化参数失效
flag.setAccessible(true);
Constructor<LazyMan> declaredConstructor=LazyMan.class.getDeclaredConstructor(null)
//破坏构造器私有化
declaredConstructor.setAccessible(true);
LazyMan instance1 =declaredConstructor.newInstance();
//更改flag值为false
flag.set(instance1,false);
LazyMan instance2 =declaredConstructor.newInstance();
//打印时抛出异常
System.out.println(instance1);
System.out.println(instance2);
}
破解了flag字段后我们发现单例依然可以被破坏
这时候就出现了枚举类单例模式
6.懒汉式单例(枚举实现)
public enum EnumSingle{
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
class Test{
public static void main (String[] args){
EnumSingle instance1 = EnumSingle.INSTANCE;
EnumSingle instance2 = EnumSingle.INSTANCE;
//打印的值为相同的
System.out.println(instance1);
System.out.println(instance2);
}
这时我们再次通过反射去破坏单例
public enum EnumSingle{
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
class Test{
public static void main (String[] args) throws Exception{
EnumSingle instance1 =EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor=EnumSingle.class.getDeclaredConstructor(null)
declaredConstructor.setAccessible(true);
EnumSingle instance2=declaredConstructor.new Instance()
//报错枚举类中没有空参的构造方法
System.out.println(instance1);
System.out.println(instance2);
}
通过jad工具反编译
发现构造器为有参构造参数为(String s,int i)
public enum EnumSingle{
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
class Test{
public static void main (String[] args) throws Exception{
EnumSingle instance1 =EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor=EnumSingle.class.getDeclaredConstructor(String.class,int.class)
declaredConstructor.setAccessible(true);
EnumSingle instance2=declaredConstructor.new Instance()
//报错不能用反射破坏单例
System.out.println(instance1);
System.out.println(instance2);
}
标签:单例,class,模式,public,LazyMan,static,饿汉,lazyMan 来源: https://blog.csdn.net/cxy_zxl/article/details/116658589