架构师之路之设计模式二:单例模式

**小弟最近在研究设计模式,准备边学边发博客,与众多大佬们交流学习,希望各位能够指出不足之处(废话不多说了,直接开花) **

今天窗外冷风飕飕的刮,已经下班的我居然不想回去,很难受;工作偷懒了一会,看了一下北京房价,天哪,我还是努力工作吧,毕竟为了本人的内人能离家近一点还是想在北京定居!我这个愁啊,何以解忧唯有怼博客啊!
咱们今天啊,怼的是单例模式,希望大家怼完这篇文章能够对单例模式有一个深刻的认识。

单例模式:顾名思义(啊呸,看不懂)只能有一个实例,无论你创建多少遍都一定是一个对象,本身私有化构造函数,本身实例化自己,对外提供唯一访问接口获取对象,这是我理解的单例哈,可能有偏差,但是问题不大哈哈。现在比较流行的或者大家所熟知的单例有懒汉式和饿汉式单例,下面咱们就这两种模式,实现一个系统的分析。

1.懒汉式:啥叫懒,就和我一样此时此刻摊在电脑椅上吗?然而并不,所谓懒汉式,即使每一次都去判断目标对象是否为null,只有为null才去创建,不为null就直接返回,懒的一批!具体代码实现首先我们需要明确一下几点:

  1. static它的特点:属于类级别,直接拿类名调用;只实例化一次在内存中唯一,常驻内存(注意加粗的部分)
  2. 空构造函数:在不手动提供构造函数的情况下默认由系统提供,是用来对类进行初始化,创建对象的。

看完以上两个特点后,咱们看一下代码,所谓单例模式,你需要私有本身对象,放置别人直接调用;私有无参构造,防止用户手动调用对象;提供公开的创建变量的方法,也就是上文提到的全局访问节点。

package com.singlecase;

import org.apache.poi.ss.formula.functions.T;

/**
 * 传说中的懒汉式单例
 * 每一次等到用的时候采取判断它是否已经实例化了  所以说它懒啊
 * @author 皇甫
 */
public class LazySingleCase {
    private static LazySingleCase lazySingleCase;

    /**
     * 私有化构造函数 不让用户手动创建只能被动获取
     */
    private LazySingleCase(){}

    /**
     * 提供返回对象的方法
     * @return
     */
    public static LazySingleCase getInstance() throws InterruptedException {
        if(lazySingleCase == null){
            lazySingleCase = new LazySingleCase();
        }
        return lazySingleCase;
    }
}

这样写固然简单,但是他有一个致命的缺陷,就是无法应对高并发模式下的情况;为什么呢?大家可以这样设想一下,当我有2条线程,第一条线程判断完为null后还没有来得及实例化对象,第二条线程就开始判断了,此时因为第一条线程还没有来得及实例化,所以造成第二条线程也为null,两个线程同时进入逻辑块,此时就创建了两个实例。所以只推荐在单线程的环境下使用它。测试一下!

package com.singlecase;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 并发单例类
 * @author 皇甫
 */
public class ConcurrentSingleton {
    private volatile boolean lock;

    public boolean isLock() {
        return lock;
    }

    public void setLock(boolean lock) {
        this.lock = lock;
    }

    /**
     * 测试并发环境下懒汉式能否保持单例
     * @return 返回对象的Hash值集合 Set能帮我们去重
     */
    public static Set<Integer> getStudentHash() throws InterruptedException {
        final Set<Integer> instanceSet = Collections.synchronizedSet(new HashSet<Integer>());
        final ConcurrentSingleton con = new ConcurrentSingleton();
        con.setLock(true);

        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for(int i = 0;i<10;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        if(!con.isLock()){
                            LazySingleCase instance = null;
                            try {
                                instance = LazySingleCase.getInstance();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            instanceSet.add(instance.hashCode());
                            break;
                        }
                    }
                }
            });
        }

        Thread.sleep(5000);
        con.setLock(false);
        Thread.sleep(5000);
        executorService.shutdown();
        return instanceSet;
    }
}

你会惊奇的发现,小概率情况下出现两个甚至多个结果,哪有什么办法能够解决呢?我就想在多线程的环境下使用懒汉式单例,怎么办,此时你可以这样搞。

package com.singlecase;

/**
 * 同步单例模式
 * @author 皇甫
 */
public class SynchronizationSingleton {
    private static SynchronizationSingleton synchronizationSingleton;
    private SynchronizationSingleton(){}

    /**
     * 简单粗暴直接将整个方法块同步
     * @return 返回类对象
     */
    public synchronized static SynchronizationSingleton getInstance(){
        if(synchronizationSingleton == null){
            synchronizationSingleton = new SynchronizationSingleton();
        }
        return synchronizationSingleton;
    }
}

就问你怕不怕,直接对整个方法加锁,不拿到锁标记,就只能等着等别人实例化完了,你再进去!看似很完美;但是---->你不会感觉慢吗?每一个都拿锁标记?疯了?那此时,再去考虑对整个代码进行重构,使他更完美,怎么搞?看看下面这种方案,使用双重锁机制来校验!

package com.singlecase;

/**
 * 单例模式-双重校验锁
 * 懒汉式模式下保证线程安全的双重锁机制
 * @author 皇甫
 */
public class DoubleLockSingleCase {
    private static DoubleLockSingleCase doubleLockSingleCase;

    private DoubleLockSingleCase(){}

    /**
     * 双重校验锁创建对象
     * @return
     */
    public static DoubleLockSingleCase getInstance(){
        if(doubleLockSingleCase == null){
            synchronized (DoubleLockSingleCase.class){
                if(doubleLockSingleCase == null){
                    doubleLockSingleCase = new DoubleLockSingleCase();
                }
            }
        }
        return doubleLockSingleCase;
    }
}


此时此刻,帅气的我要出来解释一下了,咳咳
第一个判断null和不用我多说了,不加就和上面最原始的一个没啥两样,那为什么要进行第二此判null呢?试想一下,多线程并发的情况下,如果两个线程同时进了判断null的情况下,第二个判空就是为了防止这个!
此时此刻,是不是感觉很完美,这也是教科书上或者各大网站上最标准的一版。

**饿汉式:**什么叫饿?啥来都吃(屎?滚),我只要看见了,我就创建对象,不管你能不能用上,一个饿红眼的人,只要看见吃的,他才不管好不好吃!怼代码

package com.singlecase;

/**
 * 饿汉式单例模式
 * @author 皇甫
 */
public class HungryChineseSingleCase {
    private static HungryChineseSingleCase hungryChineseSingleCase = new HungryChineseSingleCase();

    private HungryChineseSingleCase(){}

    /**
     * 直接返回实例
     * @return
     */
    public static HungryChineseSingleCase getInstance(){
        return hungryChineseSingleCase;
    }
}

这样搞天生线程安全,jvm为咱们保证的(静态,不懂的回去看开头的两句话);但是你们有没有发现一个问题?日后我调用此类任何一个东西,都会是static实例化,不会管您能不能用上,造成一个极大的空间浪费,那我应该怎么改善它呢?

package com.singlecase;

/**
 * 是使用静态内部类来实现单例模式
 * @author 皇甫
 */
public class StaticInternalClassSingleton {

    private StaticInternalClassSingleton(){}

    /**
     * 静态内部类直接实例华变量
     */
    private static class StaticInternalClassSingletonHolder{
        public static StaticInternalClassSingleton staticInternalClassSingleton = new StaticInternalClassSingleton();
    }

    /**
     * @return
     */
    public static StaticInternalClassSingleton getInstance(){
        return StaticInternalClassSingletonHolder.staticInternalClassSingleton;
    }

}

看一下这个,没毛病,就是内部静态类,日后你只有调用了getInstance()方法,我内部类才会将对象创建出来,而且能保证线程安全,哎呀我真机制?什么?还有一种?枚举!
没毛病,就是枚举,他的特点是啥
1、枚举的直接父类是java.lang.Enum,但是不能显示的继承Enum
2、枚举就相当于一个类,可以定义构造方法、成员变量、普通方法和抽象方法
3、默认私有的构造方法,即使不写访问权限也是private。(假构造器,底层没有无参数的构造器)
4、每个实例分别用于一个全局常量表示,枚举类型的对象是固定的,实例个数有限,不能使用new关键字。
5、枚举实例必须位于枚举中最开始部分,枚举实例列表的后面要有分号月其他成员相分隔
6、枚举实例后有花括号时,该实例是枚举的匿名内部类对象

废话补多少,怼代码,我要回家了,一会赶不上公交了,补充一句,我感觉枚举的方法才是最好的!

package com.singlecase;

/**
 * 枚举单例模式
 * @author 皇甫
 */
public enum  EnumerationSingleton {
    INSTANCE;
    public void whateverMethod() {
    }
}

简单吧,没用过额小伙伴,看一下测试方法吧!

package com.singlecase;

/**
 * @author 皇甫
 */
public class TestEnumerationSingleton {
    public static void main(String[] args) {
        EnumerationSingleton i1 = EnumerationSingleton.INSTANCE;
        EnumerationSingleton i2 = EnumerationSingleton.INSTANCE;
        //结果返回true
        System.out.println(i1.hashCode()==i2.hashCode());
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值