单例设计模式实现方式及破解方案

概念:
  java中单例模式是一种常见的设计模式,
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例,单例模式的写法有好几种,这里主要介绍三种 : 饿汉式单例、懒汉式单例、枚举实现单例。

单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。


一、饿汉式单例
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
public class Singleton1 {
         
         private static Singleton1 s1 = new Singleton1();
         
         private Singleton1(){
                 
         }
         
         public static Singleton1 getInstance(){
                 return s1;
         }
}


Singleton1通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

测试代码 : 
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class Singleton1Demo {
         
         public static void main(String[] args) throws Exception {
                 
                 Singleton1 s1 = Singleton1.getInstance();
                 Singleton1 s2 = Singleton1.getInstance();
                 Singleton1 s3 = Singleton1.getInstance();
                 
                 System.out.println(s1.hashCode());
                 System.out.println(s2.hashCode());
                 System.out.println(s3.hashCode());
                 
         }
         
}

测试结果

1567917652
1567917652
1567917652

破解方案 :
事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。

破解代码
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class Singleton1Demo {
         
         public static void main(String[] args) throws Exception {
                 
                 Class single1 = Class.forName( "cn.itcast.single.Singleton1" );
                 Constructor constructor = single1.getDeclaredConstructor();
                 constructor.setAccessible( true );
                 
                 Object object1 = constructor.newInstance();
                 Object object2 = constructor.newInstance();
                 Object object3 = constructor.newInstance();
                 
                 System.out.println(object1.hashCode());
                 System.out.println(object2.hashCode());
                 System.out.println(object3.hashCode());
                 
         }
         
}


测试结果
1320144062
2007692877
2031122075

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

二、懒汉式单例
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class Singleton2 {
         
         private static Singleton2 s2 = null ;
         
         private Singleton2(){
                 
         }
         
         public static Singleton2 getInstance(){
                 if (s2== null ){
                         s2 = new Singleton2();
                 }
                 return s2;
         }
         
}


以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下两种种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全 。

1).方法上加同步
[Java]  纯文本查看  复制代码
?
1
2
3
4
5
6
public static synchronized Singleton2 getInstance(){
         if (s2== null ){
                 s2 = new Singleton2();
         }
         return s2;
}


2). 使用同步代码块 , 双重检查锁定
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
public static Singleton2 getInstance(){
                 if (s2== null ){
                         synchronized (Singleton2. class ) {
                                 if (s2== null ){
                                         s2 = new Singleton2();
                                 }
                         }
                 }
                 return s2;
         }


测试代码:

[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class Singleton2Demo {
         
         public static void main(String[] args) throws Exception {
                 
                 Singleton2 s1 = Singleton2.getInstance();
                 Singleton2 s2 = Singleton2.getInstance();
                 Singleton2 s3 = Singleton2.getInstance();
                 
                 System.out.println(s1.hashCode());
                 System.out.println(s2.hashCode());
                 System.out.println(s3.hashCode());
                 
         }
         
}


测试结果
1537587829
1537587829
1537587829

破解方案 :
事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。

破解代码

public class Singleton2Demo {
        
        public static void main(String[] args) throws Exception {
                
                Class single2 = Class.forName("cn.itcast.single.Singleton2");
                Constructor[] constructors = single2.getDeclaredConstructors();
                constructors[0].setAccessible(true);
                
                Object object1 = constructors[0].newInstance();
                Object object2 = constructors[0].newInstance();
                Object object3 = constructors[0].newInstance();
                
                System.out.println(object1.hashCode());
                System.out.println(object2.hashCode());
                System.out.println(object3.hashCode());
        }
        
}

测试结果
2007692877
2031122075
668661813

三、枚举实现单例

[Java]  纯文本查看  复制代码
?
1
2
3
4
5
public enum Singleton3 {
         
         SINGLE;
         
}


测试代码
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public class Singleton3Demo {
         
         public static void main(String[] args) throws Exception {
                 
                 Singleton3 s1 = Singleton3.SINGLE;
                 Singleton3 s2 = Singleton3.SINGLE;
                 Singleton3 s3 = Singleton3.SINGLE;
                 
                 System.out.println(s1.hashCode());
                 System.out.println(s2.hashCode());
                 System.out.println(s3.hashCode());
                 
         }
         
}


测试结果

2031122075
2031122075
2031122075

简简单单的一点代码就实现了一个线程安全,与其说是写法鬼斧神工,不如说是恰如其分地应用了enum的性质。
enum有且仅有private的构造器,防止外部的额外构造,这恰好和单例模式吻合,也为保证单例性做了一个铺垫。这里展开说下这个private构造器,如果我们不去手写构造器,则会有一个默认的空参构造器。
而且枚举类型实现的单例, 是不可以通过反射进行破解的 , 测试代码如下:
[Java]  纯文本查看  复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Singleton3Demo {
         
         public static void main(String[] args) throws Exception {
                 
                 Class single3 = Class.forName( "cn.itcast.single.Singleton3" );
                 Constructor[] constructors = single3.getDeclaredConstructors();
                 Constructor constructor = constructors[ 0 ];
                 
                 constructor.setAccessible( true );
                 
                 Object object1 = constructor.newInstance();
                 Object object2 = constructor.newInstance();
                 Object object3 = constructor.newInstance();
                 
                 System.out.println(object1);
                 System.out.println(object2);
                 System.out.println(object3);
                 
                 
         }
         
}

测试结果
[Java]  纯文本查看  复制代码
?
1
2
3
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
     at java.lang.reflect.Constructor.newInstance(Constructor.java: 521 )
     at cn.itcast.single.test.Singleton3Demo.main(Singleton3Demo.java: 17 )

所以 , 对于以上实现单例的三种方式 , 枚举这种方式所实现的单例, 是线程安全的 , 是最简单的 , 也是最安全的方式 。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值