**小弟最近在研究设计模式,准备边学边发博客,与众多大佬们交流学习,希望各位能够指出不足之处(废话不多说了,直接开花) **
今天窗外冷风飕飕的刮,已经下班的我居然不想回去,很难受;工作偷懒了一会,看了一下北京房价,天哪,我还是努力工作吧,毕竟为了本人的内人能离家近一点还是想在北京定居!我这个愁啊,何以解忧唯有怼博客啊!
咱们今天啊,怼的是单例模式,希望大家怼完这篇文章能够对单例模式有一个深刻的认识。
单例模式:顾名思义(啊呸,看不懂)只能有一个实例,无论你创建多少遍都一定是一个对象,本身私有化构造函数,本身实例化自己,对外提供唯一访问接口获取对象,这是我理解的单例哈,可能有偏差,但是问题不大哈哈。现在比较流行的或者大家所熟知的单例有懒汉式和饿汉式单例,下面咱们就这两种模式,实现一个系统的分析。
1.懒汉式:啥叫懒,就和我一样此时此刻摊在电脑椅上吗?然而并不,所谓懒汉式,即使每一次都去判断目标对象是否为null,只有为null才去创建,不为null就直接返回,懒的一批!具体代码实现首先我们需要明确一下几点:
- static它的特点:属于类级别,直接拿类名调用;只实例化一次在内存中唯一,常驻内存(注意加粗的部分)
- 空构造函数:在不手动提供构造函数的情况下默认由系统提供,是用来对类进行初始化,创建对象的。
看完以上两个特点后,咱们看一下代码,所谓单例模式,你需要私有本身对象,放置别人直接调用;私有无参构造,防止用户手动调用对象;提供公开的创建变量的方法,也就是上文提到的全局访问节点。
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());
}
}