Java 编程进阶:单例模式与反射挑战

random-pic-api

每天一个 Linux 命令

less 命令

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

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

抽象工厂模式练习

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

UML

20241229154732_s52IuBOz.webp

代码

抽象工厂

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

具体工厂

java
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 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();
}
}

抽象产品

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

具体产品

java
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
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 显示");
}
}

添加配置

plaintext
1
gameType=com.dong4j.homework.WMGameFactory

测试类

java
1
2
3
4
5
6
7
8
9
10
11
@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();
}

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

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

单例模式的几种实现

饿汉模式

java
1
2
3
4
5
6
7
8
9
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();

private EagerSingleton(){}

public static EagerSingleton getINstance(){
return instance;
}
}

懒汉模式

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

多线程优化

java
1
2
3
4
5
6
7
8
9
10
11
12
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;
}
}

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

双锁机制

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}

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

静态内部类 单例

java
1
2
3
4
5
6
7
8
9
10
11
12
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;
}
}

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

枚举 单例

java
1
2
3
public enum EnumSingleton {
INSTANCE;
}

使用 EnumSingleton.INSTANCE 来访问

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

反射破坏单例模式

防止反射实例化对象

利用反射生成对象

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

解决方法

在私有构造中抛出异常

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

反序列化破坏单例模式

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}
}
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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());
}
}

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

添加 readResolve 方法

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

克隆破坏单例模式

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

利用枚举防止破坏

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

使用反射破解枚举单例:
运行结果是抛出异常: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 方法如下:

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

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

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

java
1
2
3
4
5
6
7
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");
}

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

引用