设计模式学习笔记(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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值