Java 设计模式之单例模式

最近打算再回头系统的看看Java 的设计模式,Java 有23种设计模式,就先从最简单的单例模式开始吧。
我们常见的单例模式无非就是所谓的 懒汉式饿汉式, 除此之外呢,还有 双同步锁模式静态内部类模式,以及枚举模式等。

一,常见写法

先看看常见的饿汉式和懒汉式吧

饿汉式:
/**
 *  单例模式-饿汉式(线程安全,效率高,因为在类加载时对对象进行初始化,所以叫做饿汉式,饿的等不及)
 */
public class SingletonHungry {

    private static SingletonHungry instance = new SingletonHungry();

    private SingletonHungry(){

    }

    public static SingletonHungry getInstance() {
        return instance;
    }
}
/**
 *  单例模式-懒汉模式(线程安全的,加载效率低,因为要加同步锁)
 */
public class SingletonLazy {
    private static SingletonLazy instance;

    private SingletonLazy(){

    }

    public static synchronized SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }

        return instance;
    }
}

饿汉式和懒汉式的主要区别是对象的初始化时间不同,所以它们的执行效率也是不同的,饿汉式对象的创建是在类的初始化的时候就创建了,而懒汉式是在我们调用的时候才进行初始化。而懒汉式由于有同步锁,所以它的执行效率相对比较低,后面我们会进行测试比较各个模式的执行效率。

而我们在代码中常用的单例模式是懒汉式,只不过多加了一重锁,所以也就是双重锁模式,看代码;

/**
 * 单例模式-双重锁模式;
 * 接下来我解释一下在并发时,双重校验锁法会有怎样的情景:
 *
 *  STEP 1. 线程A访问getInstance()方法,因为单例还没有实例化,所以进入了锁定块。
 *
 *  STEP 2. 线程B访问getInstance()方法,因为单例还没有实例化,得以访问接下来代码块,而接下来代码块已经被线程1锁定。
 *
 *  STEP 3. 线程A进入下一判断,因为单例还没有实例化,所以进行单例实例化,成功实例化后退出代码块,解除锁定。
 *
 *  STEP 4. 线程B进入接下来代码块,锁定线程,进入下一判断,因为已经实例化,退出代码块,解除锁定。
 *
 *  STEP 5. 线程A初始化并获取到了单例实例并返回,线程B获取了在线程A中初始化的单例。
 *
 *  理论上双重校验锁法是线程安全的,并且,这种方法实现了lazyloading。
 */
public class SingletonDoubleLock {

    private static SingletonDoubleLock instance;

    private SingletonDoubleLock() {

    }

    public static synchronized SingletonDoubleLock getInstance() {
        if (instance == null) {
            synchronized (SingletonDoubleLock.class) {
                if (instance == null) {
                    instance = new SingletonDoubleLock();
                }
            }
        }

        return instance;
    }
}

为什么要再加一个锁呢,上面的注释已经写的很清楚了,大伙可以看看。

另外再写一下静态内部类模式和枚举模式吧,这两种及少用到

/**
 * 单例模式-静态内部类模式
 */

public class SingletonStatic {

    private static class SingletonStaticClass {
        private static final SingletonStatic instance = new SingletonStatic();
    }

    private SingletonStatic() {

    }

    public static SingletonStatic getInstance() {
        return SingletonStaticClass.instance;
    }
}
**
 * d单例模式-枚举单例
 */

public enum  SingletonEnum {
    INSTANCE; // 枚举类型本来就是一个单例

    /**
     * 添加自己的操作
     * */
    public void operate() {

    }
}

二,测试是不是只生成一个对象

在这里我们写了一个SingletonClient类,用来测试我们的单例

ublic class SingletonClient {
     public static void main(String[] args) {
         SingletonHungry s1 = SingletonHungry.getInstance();
         SingletonHungry s2 = SingletonHungry.getInstance();

         SingletonLazy s3 = SingletonLazy.getInstance();
         SingletonLazy s4 = SingletonLazy.getInstance();

         SingletonDoubleLock s5 = SingletonDoubleLock.getInstance();
         SingletonDoubleLock s6 = SingletonDoubleLock.getInstance();

         SingletonStatic s7 = SingletonStatic.getInstance();
         SingletonStatic s8= SingletonStatic.getInstance();

         System.out.println(s1 + "===" + s2);
         System.out.println(s3 + "===" + s4);
         System.out.println(s5 + "===" + s6);
         System.out.println(s7 + "===" + s8);
     }
}

我们对上面的每个单例模式进行打印比较,看看是不是对象的值是同一个

com.hwang.designpattern.singleton.SingletonHungry@7e0b37bc===com.hwang.designpattern.singleton.SingletonHungry@7e0b37bc
com.hwang.designpattern.singleton.SingletonLazy@3b95a09c===com.hwang.designpattern.singleton.SingletonLazy@3b95a09c
com.hwang.designpattern.singleton.SingletonDoubleLock@6ae40994===com.hwang.designpattern.singleton.SingletonDoubleLock@6ae40994
com.hwang.designpattern.singleton.SingletonStatic@1a93a7ca===com.hwang.designpattern.singleton.SingletonStatic@1a93a7ca

这里是打印出的结果,可见他们是同一个对象,对象只重新创建了一次。

三,测试各个模式执行效率

 int threadNum = 10; // 线程数
         CountDownLatch countDownLatch = new CountDownLatch(threadNum);
         long startTime = System.currentTimeMillis();// 开始时间
         for (int j = 0; j < threadNum; j++) {
             new Thread(new Runnable() {
                 @Override
                 public void run() {
                     for (int i = 0; i < 1000000; i++) {
                         SingletonHungry.getInstance();
                     }

                     countDownLatch.countDown();
                 }
             }).start();
         }

         countDownLatch.await(); // main 线程阻塞,直到计数变为0,才会继续往下执行
         long endTime =  System.currentTimeMillis();
         System.out.println("饿汉式耗时时间为:" + (endTime - startTime));

这里引用 CountDownLatch 这个类呢,是因为我们的计时是在主线程中的,而对象的获取执行是在子线程中,是异步的,所以加入了计时,当子线程执行完成时候,在执行main线程的endTime。
最后看看测试结果:

饿汉式耗时时间为:19
懒汉式耗时时间为:540
双重锁式耗时时间为:320
内部静态类式耗时时间为:82

可见饿汉式以及内部静态类由于没有同步锁,执行效率还是很高的。
写的很简单,有不正确的地方还望各位朋友指出,一起交流学习。

详细代码请移步github:https://github.com/Trac-MacGrady/DesignPattern/tree/master

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值