学习设计模式,就是将设计者的思维融入学习和工作中,更高层次的思考,而不是死记硬背模式代码。
GOF将设计模式分为以下三个模块:
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
实现代码
package com.tumbler.singleton;
/**
* User:tumbler
* Desc:单例模式:饿汉式
*/
public class SingletonDemo1 {
//类初始化时立即加载此对象,天然线程安全,没有延时加载
private static SingletonDemo1 instance = new SingletonDemo1();
//构造方法私有化
private SingletonDemo1(){}
//提供外部访问实例方法,方法没有同步,调用效率高
public static SingletonDemo1 getInstance(){
return instance;
}
}
测试
package com.tumbler.singleton;
/**
* User:tumbler
* Desc:测试单例模式
*/
public class Client {
public static void main(String[] args){
SingletonDemo1 instance1 = SingletonDemo1.getInstance();
SingletonDemo1 instance2 = SingletonDemo1.getInstance();
System.out.println(instance1 == instance2); // true
}
}
分析
代码实现
package com.tumbler.singleton;
/**
* User:tumbler
* Desc:单例模式:懒汉式
*/
public class SingletonDemo2 {
//类初始化时不初始化此对象,延时加载
private static SingletonDemo2 instance;
//私有化构造器
private SingletonDemo2() {}
//方法同步,调用效率低
public static synchronized SingletonDemo2 getInstance(){
if (instance == null) {
instance = new SingletonDemo2();
}
return instance;
}
}
测试
package com.tumbler.singleton;
/**
* User:tumbler
* Desc:测试单例模式
*/
public class Client {
public static void main(String[] args){
SingletonDemo2 instance1 = SingletonDemo2.getInstance();
SingletonDemo2 instance2 = SingletonDemo2.getInstance();
System.out.println(instance1 == instance2); // true
}
}
分析
代码实现
package com.tumbler.singleton;
/**
* User:tumbler
* Desc:单例模式:双重校验锁
*/
public class SingletonDemo3 {
private volatile static SingletonDemo3 instance;
private SingletonDemo3() {}
public static SingletonDemo3 getInstance(){
if (instance == null) {
synchronized (SingletonDemo3.class) {
if (instance == null) {
instance = new SingletonDemo3();
}
}
}
return instance;
}
}
测试
package com.tumbler.singleton;
/**
* User:tumbler
* Desc:测试单例模式
*/
public class Client {
public static void main(String[] args){
SingletonDemo3 instance1 = SingletonDemo3.getInstance();
SingletonDemo3 instance2 = SingletonDemo3.getInstance();
System.out.println(instance1 == instance2); // true
}
}
分析
代码
package com.tumbler.singleton;
/**
* User:tumbler
* Desc:单例模式:静态内部类
*/
public class SingletonDemo4 {
private static class SingletonClassInstance {
private static final SingletonDemo4 instance = new SingletonDemo4();
}
private SingletonDemo4() {}
public static SingletonDemo4 getInstance() {
return SingletonClassInstance.instance;
}
}
测试
package com.tumbler.singleton;
/**
* User:tumbler
* Desc:测试单例模式
*/
public class Client {
public static void main(String[] args){
SingletonDemo4 instance1 = SingletonDemo4.getInstance();
SingletonDemo4 instance2 = SingletonDemo4.getInstance();
System.out.println(instance1 == instance2); // true
}
}
分析
代码
package com.tumbler.singleton;
/**
* User:tumbler
* Desc:单例模式:枚举实现
*/
public enum SingletonDemo5 {
//定义一个枚举元素,它就代表了一个SingletonDemo的对象
INSTANCE;
/**
* 单例可以有自己的操作
*/
public void singletonOperation() {
//功能处理
}
}
测试
package com.tumbler.singleton;
/**
* User:tumbler
* Desc:测试单例模式
*/
public class Client {
public static void main(String[] args){
SingletonDemo5 instance1 = SingletonDemo5.INSTANCE;
SingletonDemo5 instance2 = SingletonDemo5.INSTANCE;
System.out.println(instance1 == instance2); // true
}
}
分析
使用CountDownLatch:同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
下面是我测试的结果(10个线程调用1000000次),大家关注相对值即可,不同环境下的程序测试值完全不一样。
设计模式 | 时间(ms) |
---|---|
饿汉式 | 71 |
懒汉式 | 455 |
静态内部类式 | 77 |
双重校验锁式 | 83 |
枚举式 | 84 |
测试代码示例
package com.tumbler.singleton;
import java.util.concurrent.CountDownLatch;
/**
* User:tumbler
* Desc:测试比较五种方式单例模式的效率
*/
public class TestSingletonDemo {
public static void main(String[] args) throws Exception{
long start = System.currentTimeMillis();
int threadNum = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum; i++) {
new Thread(() -> {
for (int j = 0; j < 1000000; j++) {
//Object o = SingletonDemo1.getInstance();
Object o = SingletonDemo5.INSTANCE;
}
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start));
}
}
测试代码
package com.tumbler.singleton;
import java.lang.reflect.Constructor;
/**
* User:tumbler
* Desc:测试反射破坏单例模式,以懒汉式为例
*/
public class TestReflex {
public static void main(String[] args) throws Exception {
SingletonDemo2 instance1 = SingletonDemo2.getInstance();
SingletonDemo2 instance2 = SingletonDemo2.getInstance();
System.out.println(instance1 == instance2); // true 单例
//通过反射破坏单例模式
Class clazz = (Class) Class.forName("com.tumbler.singleton.SingletonDemo2");
Constructor constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true); // 访问私有构造器
SingletonDemo2 instance3 = constructor.newInstance();
SingletonDemo2 instance4 = constructor.newInstance();
System.out.println(instance3 == instance4); //false 单例被破坏
}
}
如何防止?
在私有构造器内抛出异常,即多次构造直接报错阻止。
修改SingletonDemo2:
package com.tumbler.singleton;
/**
* User:tumbler
* Desc:单例模式:懒汉式 防止反射破坏单例
*/
public class SingletonDemo2 {
//类初始化时不初始化此对象,延时加载
private static SingletonDemo2 instance;
//私有化构造器
private SingletonDemo2() {
if(instance != null) {
throw new RuntimeException();
}
}
//方法同步,调用效率低
public static synchronized SingletonDemo2 getInstance(){
if (instance == null) {
instance = new SingletonDemo2();
}
return instance;
}
}
要是用序列化,则先给SingletonDemo2实现序列化接口
public class SingletonDemo2 implements Serializable
测试代码
package com.tumbler.singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* User:tumbler
* Desc:通过反序列化破坏单例模式
*/
public class TestDeserialize {
public static void main(String[] args) throws Exception{
SingletonDemo2 instance1 = SingletonDemo2.getInstance();
SingletonDemo2 instance2 = SingletonDemo2.getInstance();
System.out.println(instance1 == instance2); // true 单例
//序列化
FileOutputStream fos = new FileOutputStream("D:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance1);
oos.close();
fos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/a.txt"));
SingletonDemo2 instance3 = (SingletonDemo2) ois.readObject();
System.out.println(instance1 == instance3); // false 破坏单例
}
}
如何防止
通过定义readResolve()方法阻止破坏。在SingletonDemo2 添加以下方法即可:
//反序列化
private Object readResolve() throws ObjectStreamException {
return instance;
}