设计模式学习笔记(1)
前言
今天学习单例模式和原型模式。
正文
单例模式
单例模式:主要是为了实现资源共享,只需要初始化一次,后续就能够重复使用,而不需要重新创建。
实现方式
- 饿汉式
- 懒汉式
- 注册登记式(枚举式)
- 反序列化
饿汉式单例
在类加载的时候,就直接创建实例对象
优点
- 没有加锁,执行效率高
- 线程安全,在类加载之前就已经创建
- 用户体验上来说,比懒汉式好
缺点
- 类加载的时候就初始化,没有考虑该类是否被使用了,存在资源浪费的可能。
代码实现
package com.carlos.singleton.hungry;
/**
* 饿汉式单例模式
* * 优点:线程安全,在线程产生前就已经创建
* 每次调用不用重新创建,执行效率高
* 缺点:占用资源
* * Created by carlos on 2018/11/27.
* @author carlos
*/
public class Hungry {
/**
私有化的构造方法
*/
private Hungry(){
}
// 类加载的时候就进行实例化
private static Hungry hungry = new Hungry();
/**
* 供使用者调用,直接返回早就创建好的实例
*/
public static Hungry getInstance(){
return hungry;
}
}
懒汉式单例
默认类加载的时候不实例化,当第一次用到的时候才创建,后续再用到也不用再创建。
优点
- 不会浪费资源,只有需要的时候才创建
缺点
- 线程不安全
- 执行效率低
代码实现
package com.carlos.singleton.lazy;
/**
* 懒汉式单例
* 在类加载的时候不会初始化
* Created by solrSky on 2018/11/27.
* @author solrSky
*/
public class LazyOne {
private LazyOne(){
}
// 静态块,公共内存区域
private static LazyOne lazyOne = null;
public static LazyOne GetInstance(){
// 线程不安全,可能两个线程都会进入到这个if里
if (null == lazyOne){
lazyOne = new LazyOne();
}
return lazyOne;
}
}
测试效果
利用CountDownLatch来模拟多线程并发的效果。
package com.carlos.singleton.test;
import com.carlos.singleton.lazy.LazyOne;
import java.util.concurrent.CountDownLatch;
/**
* @author Solrsky
* @date 2018/11/28
*/
public class ThreadSafeTest {
public static void main(String[] args) {
int count = 2000;
CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++){
new Thread(){
@Override
public void run() {
try{
latch.await(); // 阻塞
LazyOne lazyOne = LazyOne.GetInstance();
System.out.println(lazyOne.toString());
}catch (Exception e){
}
}
}.start();
latch.countDown();
}
}
}
结果
解决线程不安全问题
1、加锁(synchronized)
package com.carlos.singleton.lazy;
/**
* @author Solrsky
* @date 2018/11/28
*/
public class LazyTwo {
public LazyTwo() {
}
// 静态块,公共内存区域
private static LazyTwo lazyTwo = null;
// 加锁
public synchronized static LazyTwo GetInstance(){
// 线程不安全,可能两个线程都会进入到这个if里
if (null == lazyTwo){
lazyTwo = new LazyTwo();
}
return lazyTwo;
}
}
测试性能:
package com.carlos.singleton.test;
import com.carlos.singleton.hungry.Hungry;
import com.carlos.singleton.lazy.LazyOne;
import com.carlos.singleton.lazy.LazyTwo;
/**
* Created by dingjunjie on 2018/11/27.
*
* @author dingjunjie
*/
public class Test {
public static void main(String[] args) {
Hungry hungry = Hungry.getInstance();
System.out.println(hungry.toString());
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++){
LazyOne lazyOne = LazyOne.GetInstance();
}
System.out.println("lazyOne耗时:");
System.out.println(System.currentTimeMillis()-start);
long start2 = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++){
LazyTwo lazyTwo = LazyTwo.GetInstance();
}
System.out.println("lazyTwo耗时:");
System.out.println(System.currentTimeMillis()-start2);
}
}
显然,加锁后,性能明显下降,那如何在保证线程安全的情况下,又不影响性能呢?请看第三种懒汉式单例实现方式。
线程安全且不影响性能的懒汉式单例实现方式
package com.carlos.singleton.lazy;
/**
* @author Solrsky
* @date 2018/11/28
*/
// 懒汉式单例
// 特点:在外部类被调用的时候内部类才会被加载
public class LazyThree {
// 使用LazyThree的时候,会先初始化内部类
// 没有使用的情况下,内部类不会加载
private static boolean initialized = false;
private LazyThree() {
// 防止反射入侵
synchronized (LazyThree.class){
// 只有第一次初始化的时候才能成功。第二次开始就
if (initialized == false){
initialized = !initialized;
}else{
throw new RuntimeException("单例被侵犯!");
}
}
}
// 每个关键字都是必要的
// static 共享单例实例
// final 保证不会被重写、重载
public static final LazyThree getInstance(){
// 在返回结果前,会先加载LazyHolder,同时也已经创建了LazyThree实例。
return LazyHolder.lazyThree;
}
// 内部静态类,在外部类被调用时,才会加载,同时会将LazyThree实例化为静态值。
private static class LazyHolder{
private static final LazyThree lazyThree = new LazyThree();
}
}
使用静态内部类来实现。
为什么静态内部类能实现线程安全呢?
首先,静态内部类在虚拟机进行加载外部类的时候不会加载,只有当外部类被调用的时候才会被加载。
根据虚拟机的机制,虚拟机会保证一个类的构造器()方法在多线程环境中被正确地加载,同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的构造器()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。
这就保证了只会初始化一个内部类,那么对应的外部类的实例也就只有一个了,保证了线程安全。
注册式单例
代码实现
package com.carlos.singleton.register;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Solrsky
* @date 2018/11/28
*/
// spring 中就是用这种注册式单例来实现的
public class BeanFactory {
private BeanFactory() {
}
// 使用ConcurrentHashMap来保证线程安全
private static Map<String,Object> ioc = new ConcurrentHashMap<>();
public static Object getBean(String className){
// 若map已有,则直接返回该实例对象
if(ioc.containsKey(className)){
return ioc.get(className);
}else{
try {
// 若map中还没有,则利用反射,获取对象实例,并注册到map中,以便下次使用
Object obj = Class.forName(className).newInstance();
ioc.put(className, obj);
return obj;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
}
反序列化可能会破坏单例
如何解决反序列化可能破坏单例的问题?
这里就要涉及到jdk的类ObjectInputStream的源码了。
jdk利用ObjectInputStream来进行反序列化,而在反序列话的时候,会去判断该类是否有readResolve方法,如果有,就不会使用反序列化出来的实例了,从而实现了防止反序列化破坏单例。
具体看代码:
ObjectInputStream中有个readUnshared()方法
public Object readUnshared() throws IOException, ClassNotFoundException {
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
Object obj = readObject0(true);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
经过层层调用,最终会访问一个方法
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
}
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
}
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
handles.finish(passHandle);
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
最终,会从内存中来读取已经存在的那个实例,而不会重新创建一个实例对象。
代码实现
package com.carlos.singleton.seriable;
import java.io.Serializable;
/**
* @author Solrsky
* @date 2018/11/29
*/
// 反序列化时可能导致破坏单例
public class Seriable implements Serializable {
// 序列化
// 反序列化
public final static Seriable INSTANCE = new Seriable();
private Seriable(){}
public static Seriable getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}