A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

本帖最后由 西安Java组 于 2017-12-28 00:02 编辑

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

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


一、饿汉式单例
[Java] 纯文本查看 复制代码
public class Singleton1 {
        
        private static Singleton1 s1 = new Singleton1();
        
        private Singleton1(){
                
        }
        
        public static Singleton1 getInstance(){
                return s1;
        }
}


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

测试代码 :
[Java] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
        public static synchronized Singleton2 getInstance(){
                if(s2==null){
                        s2 = new Singleton2();
                }
                return s2;
        }


2). 使用同步代码块 , 双重检查锁定
[Java] 纯文本查看 复制代码
public static Singleton2 getInstance(){
                if(s2==null){
                        synchronized (Singleton2.class) {
                                if(s2==null){
                                        s2 = new Singleton2();
                                }
                        }
                }
                return s2;
        }


测试代码:

[Java] 纯文本查看 复制代码
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] 纯文本查看 复制代码
public enum Singleton3 {
        
        SINGLE;
        
}


测试代码
[Java] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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] 纯文本查看 复制代码
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)

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















0 个回复

您需要登录后才可以回帖 登录 | 加入黑马