单例模式


1/20/2017 设计模式

创建者模式之单例模式

每天一个 Linux 命令

less 命令

less 命令 的作用与 more 十分相似, 都可以用来浏览文字档案的内容, 不同的是 less 命令允许用户向前或向后浏览文件, 而 more 命令只能向前浏览. 用 less 命令显示文件时, 用 PageUp 键向上翻页, 用 PageDown 键向下翻页. 要退出 less 程序, 应按 Q 键.

-e: 文件内容显示完毕后, 自动退出;
-f: 强制显示文件;
-g: 不加亮显示搜索到的所有关键词, 仅显示当前显示的关键字, 以提高显示速度;
-l: 搜索时忽略大小写的差异;
-N: 每一行行首显示行号;
-s: 将连续多个空行压缩成一行显示;
-S: 在单行显示较长的内容, 而不换行显示;
-x<数字>: 将TAB字符显示为指定个数的空格字符. 
1
2
3
4
5
6
7
8

抽象工厂模式练习

Sunny 软件公司欲推出一款新的手机游戏软件, 该软件能够支持 Symbian、Android 和 Windows Mobile 等多个智能手机操作系统平台, 针对不同的手机操作系统, 该游戏软件提供了不同的游戏操作控制 (OperationController) 类和游戏界面控制 (InterfaceController) 类, 并提供相应的工厂类来封装这些类的初始化过程. 软件要求具有较好的扩展性以支持新的操作系统平台, 为了满足上述需求, 试采用抽象工厂模式对其进行设计.

UML

代码

抽象工厂

public interface GameFactory {
    OperationController createOperationController();
    InterfaceController createInterfaceController();
}
1
2
3
4

具体工厂

public class SymbianGameFactory implements GameFactory{
    @Override
    public OperationController createOperationController() {
        return new SymbianOperationController();
    }

    @Override
    public InterfaceController createInterfaceController() {
        return new SymbianInterfaceController();
    }
}

public class AndroidGameFactory implements GameFactory{
    @Override
    public OperationController createOperationController() {
        return  new AndroidOperationController();
    }

    @Override
    public InterfaceController createInterfaceController() {
        return  new AndroidInterfaceController();
    }
}

public class WMGameFactory implements GameFactory{
    @Override
    public OperationController createOperationController() {
        return new WMOperationController();
    }

    @Override
    public InterfaceController createInterfaceController() {
        return new WMInterfaceController();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

抽象产品

public interface OperationController {
    void play();
}
public interface InterfaceController {
    void show();
}
1
2
3
4
5
6

具体产品

public class SymbianOperationController implements OperationController {
    @Override
    public void play() {
        System.out.println("Symbian 系统操作");
    }
}
public class SymbianInterfaceController implements InterfaceController {
    @Override
    public void show() {
        System.out.println("Symbian 显示");
    }
}
public class AndroidOperationController implements OperationController {
    @Override
    public void play() {
        System.out.println("Android 操作");
    }
}
public class AndroidInterfaceController implements InterfaceController {
    @Override
    public void show() {
        System.out.println("Android 显示");
    }
}
public class WMOperationController implements OperationController {
    @Override
    public void play() {
        System.out.println("WM 操作");
    }
}
public class WMInterfaceController implements InterfaceController {
    @Override
    public void show() {
        System.out.println("WM 显示");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

添加配置

gameType=com.dong4j.homework.WMGameFactory
1

测试类

 @Test
    public void gameTest() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        GameFactory gameFactory;
        OperationController operationController;
        InterfaceController interfaceController;
        gameFactory = (GameFactory) ConfigUtil.getType("gameType");
        operationController = gameFactory.createOperationController();
        interfaceController = gameFactory.createInterfaceController();
        operationController.play();
        interfaceController.show();
    }
1
2
3
4
5
6
7
8
9
10
11

创建者模式之三: 单例模式

确保某一个类只有一个实例, 而且自行实例化并向整个系统提供这个实例, 这个类称为单例类, 它提供全局访问的方法

单例模式的几种实现

饿汉模式

public class EagerSingleton {
    private  static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton(){}

    public static EagerSingleton getINstance(){
        return instance;
    }
}
1
2
3
4
5
6
7
8
9

懒汉模式

public class LazySingleton {
    private static LazySingleton instance = null;
    private LazySingleton(){}
    public static LazySingleton getInstance(){
        return new LazySingleton();
    }
}
1
2
3
4
5
6
7

多线程优化

public class SynchronizedLazySingleton {
    private static SynchronizedLazySingleton instance = null;
    private SynchronizedLazySingleton(){}
    public static SynchronizedLazySingleton getInstance(){
        if(instance == null){
            synchronized (SynchronizedLazySingleton.class){
                instance = new SynchronizedLazySingleton();
            }
        }
        return instance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

存在的问题: 当 A, B 2个线程调用 getInstance() 时,进入 if 判断,如果此时为 null, 怎么排队进入创建对象的同步块, 当 A 创建完并返回了一个单例对象时, 线程 B 进入同步块,再次创建一个新的对象.

双锁机制

public class DoubleSynchronizedLazySingleton {
    private volatile static DoubleSynchronizedLazySingleton instance = null;
    private DoubleSynchronizedLazySingleton(){}
    public static DoubleSynchronizedLazySingleton getInstance(){
        if(instance == null){
            synchronized (DoubleSynchronizedLazySingleton.class){
                if(instance == null){
                    instance = new DoubleSynchronizedLazySingleton();
                }
            }
        }
        return instance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

instance 必须用 volatile 修饰, volatile 在这里的作用是禁止重排序

静态内部类 单例

public class SynchronizedLazySingleton {
    private static SynchronizedLazySingleton instance = null;
    private SynchronizedLazySingleton(){}
    public static SynchronizedLazySingleton getInstance(){
        if(instance == null){
            synchronized (SynchronizedLazySingleton.class){
                instance = new SynchronizedLazySingleton();
            }
        }
        return instance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

使用 classload 机制来保证初始化 instance 时只有一个线程, 只有在显示调用getInstance() 时才会创建单例对象

枚举 单例

public enum EnumSingleton {
    INSTANCE;
}
1
2
3

使用 EnumSingleton.INSTANCE 来访问

防止单例模式被 反射, 反序列化, 克隆破坏

反射破坏单例模式

防止反射实例化对象

利用反射生成对象

//使用反射破坏单例模式
Class c = Class.forName(Singleton.class.getName());  
Constructor constructor = c.getDeclaredConstructor();  
// 能访问私有构造方法
constructor.setAccessible(true);  
// 利用私有构造方法创建一个新的单例对象,破坏单例模式
Singleton singleton = (Singleton)ct.newInstance(); 
1
2
3
4
5
6
7

解决方法

在私有构造中抛出异常

public class LazySingleton {
    private static LazySingleton instance = null;
    private LazySingleton() throws Exception {
        throw new Exception();
    }
    public static LazySingleton getInstance() throws Exception {
        return new LazySingleton();
    }
}
1
2
3
4
5
6
7
8
9

反序列化破坏单例模式

import java.io.Serializable;
/**
 * 使用双重校验锁方式实现单例
 */
public class Singleton implements Serializable{
    private volatile static Singleton singleton;
    private Singleton (){}
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SerializableDemo1 {
    //为了便于理解, 忽略关闭流操作及删除文件操作. 真正编码时千万不要忘记
    //Exception直接抛出
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //Write Obj to file
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        oos.writeObject(Singleton.getSingleton());
        //Read Obj from file
        File file = new File("tempFile");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        Singleton newInstance = (Singleton) ois.readObject();
        //判断是否是同一个对象
        System.out.println(newInstance == Singleton.getSingleton());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

防止序列化反序列化破坏单例的方法:

添加 readResolve 方法

private Object readResolve() {
        return singleton;
    }
1
2
3

克隆破坏单例模式

由克隆我们可以想到原型模式, 原型模式就是通过 clone 方法实现对象的创建的, clone 方式是 Object 方法, 每个对象都有, 那我使用一个单例模式类的对象, 调用 clone 方法, 再创建一个新的对象了, 那岂不是上面说的单例模式失效了. 当然答案是否定, 某一个对象直接调用 clone 方法, 会抛出异常, 即并不能成功克隆一个对象. 调用该方法时, 必须实现一个 Cloneable 接口. 这也就是原型模式的实现方式. 还有即如果该类实现了 cloneable 接口, 尽管构造函数是私有的, 他也可以创建一个对象. 即 clone 方法是不会调用构造函数的, 他是直接从内存中 copy 内存区域的. 所以单例模式的类是不可以实现 cloneable 接口的.

利用枚举防止破坏

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}
1
2
3
4
5
6

使用反射破解枚举单例: 运行结果是抛出异常:Exception in thread "main" java.lang.NoSuchMethodException: cn.xing.test.Weekday.() 明明Weekday有一个无参的构造函数, 为何不能通过暴力反射访问? 最新的Java Language Specification (§8.9)规定: Reflective instantiation of enum types is prohibited. 这是java语言的内置规范.

使用 clone 破解枚举单例 所有的枚举类都继承自java.lang.Enum类, 而不是Object类. 在java.lang.Enum类中clone方法如下:

protected final Object clone() throws CloneNotSupportedException {  
    throw new CloneNotSupportedException();  
}  
1
2
3

调用该方法将抛出异常, 且final意味着子类不能重写clone方法, 所以通过clone方法获取新的对象是不可取的.

使用序列化破解枚举单例 java.lang.Enum类的readObject方法如下:

private void readObject(ObjectInputStream in) throws IOException,  
        ClassNotFoundException {  
            throw new InvalidObjectException("can't deserialize enum");  
}  
private void readObjectNoData() throws ObjectStreamException {  
        throw new InvalidObjectException("can't deserialize enum");  
} 
1
2
3
4
5
6
7

同暴力反射一样, Java Language Specification (§8.9)有着这样的规定: the special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization.

引用

Last Updated: 7/3/2019, 6:17:56 PM