一起学设计模式-单例

2021-07-06 17:28:12  晓掌柜  版权声明:本文为站长原创文章,转载请写明出处


一、前言

        今天我们学习设计模式中一个比较常用的种类:单例模式。同样,我们会从:概念、解决问题、使用

    场景、代码示例等角度做一个全方位的剖析。跟着我的节奏,冲!

二、单例模式基本介绍

    2.1、概念

        单例模式是采用一定的方式来保证在整个系统软件中,对某个类仅存在一个实例对象,并且只提供一个

    获取其对象实例的方法(静态方法)。

    2.2、单例模式的优点

        由于这个类只被实例化一次,也就是说不管有多少个类使用到了这个类,也都是只有一个该类的对象。

    因此,单例从程序上来说,减少了类实例对象的创建、减小了GC压力、减少了系统资源消耗达到了提升程序

    性能的效果。

    2.3、经典实用场景

        ① 网站计数器:会使用单例,已达到全局同步的效果

        ② 日志应用程序:一般日志程序都要实时开放,并接受外部调用,使用单例可以更快速高效

        ③ 数据库连接池:频繁的数据库连接的打开和关闭非常消耗资源,使用单例维护可大大缓解上述问题

        ④ 多线程的线程池设计:更加方便的对线程池中的线程进行维护和控制处理

        ⑤ Spring的bean管理器:这个在后续会有专门的文章进行详细介绍

三、实现方式(饿汉式-静态常量)

    3.1、步骤如下

        ① 构造器私有化(放置被其他类new)

        ② 类的内部创建对象

        ③ 对外暴露一个静态的公共方法以获取实例

    3.2、代码实现


/**  
* @author XA
* date 2021/7/6 16:41
* description: 饿汉式(静态常量)
*/
public class Singletion1 {

/* 构造器私有化 */
private Singletion1(){};

/* 提供静态常量Singletion1,创建实例对象 */
private static Singletion1 instance = new Singletion1();

/* 对外的静态公共方法 */
public static Singletion1 getInstance(){
return instance;
}

}

    3.3、优缺点及结论

        优点:写法简单,在类加载时就创建了实例对象,避免了线程同步问题。

        缺点:在类加载时就完成了实例化,没有达到lazy loading的效果,会导致内存浪费

        结论:可以使用,但是会造成内存浪费

四、饿汉式(静态代码块)

    4.1、步骤如下

        ① 私有化构造器

        ② 在静态代码块中创建对象

        ③ 对外暴露私有公共方法以获取实例

    4.2、代码示例


/**  
* @author XA
* date 2021/7/6 16:50
* description: 饿汉式-静态代码块
*/
public class Singletion2 {

private Singletion2(){};

private static Singletion2 instance;

static{
instance = new Singletion2();
}

public Singletion2 getInstance(){
return instance;
}

}

    4.3、优缺点

        和饿汉式的静态常量的实现方式并无太大差异,只不过是将类的实例化放在了静态代码块中。

        同样在类状态时初始化,会导致内存浪费!

五、懒汉式(线程不安全)

    5.1、实现步骤

        ① 私有构造方法

        ② 声明静态变量

        ③ 对外暴露静态公共方法

    5.2、代码示例


/**  
* @author XA
* date 2021/7/6 16:50
* description: 饿汉式-静态代码块
*/
public class Singletion2 {

private Singletion2(){};

private static Singletion2 instance;

static{
instance = new Singletion2();
}

public Singletion2 getInstance(){
return instance;
}

}

    5.3、优缺点

        优点:达到了lazy loading的效果。在使用时获取其实例对象,节省内存

        缺点:只能单线程使用,多线程时会产生多个实例对象。不建议使用此方法

六、懒汉式(线程安全-同步方法)

    6.1、实现步骤

        ① 私有构造方法

        ② 声明静态变量

        ③ 对外暴露静态公共方法

        ④ 使用synchronized锁住getInstance方法

    6.2、示例代码


/**
* @author XA
* date 2021/7/6 17:03
* description: 懒汉式线程安全-同步方法
*/
public class Singletion4 {

private static Singletion4 instance;

private Singletion4(){};

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

}

    6.3、优缺点

        优点:解决了线程安全问题

        缺点:锁住了整个方法,效率太低了,不建议在开发中使用

七、懒汉式(线程安全-双重检查)

    7.1、实现步骤

        ① 私有构造方法

        ② 声明静态变量

        ③ 对外暴露静态公共方法

        ④ 使用synchronized锁,进行两次null的检查

    7.2、代码示例


/**
* @author XA
* date 2021/7/6 17:09
* description: 懒汉式-线程安全-双重检查
*/
public class Singletion5 {

private static Singletion5 instance;

private Singletion5(){};

private static Singletion5 getInstance(){
if(instance == null){
synchronized (Singletion5.class){
if(instance == null){
instance = new Singletion5();
}
}
}
return instance;
}

}

    7.3、优缺点

        ① 进行了两次instance == null的检查,保证线程安全

        ② 实例化代码只执行一次

        ③ 线程安全、延迟加载、效率高

        ④ 建议在实际开发中使用

八、线程池单例的实操代码示例


/**
* @author XA
* date 2021/7/6 8:56
* description: 单例线程池
*/
public class SingletionThredPool {

/* 核心线程池大小:线程池维护的最小线程数,即使处于空闲状态也不会销毁。默认1 */
private static final int CORE_POOL_SIZE = 50;
/* 线程池最大数量:线程池不会无限制的创建线程,这里是最大容量。默认Integer.MAX_VALUE */
private static final int MAXIMUNPOOL_SIZE = 1000;
/* 空闲线程存活时间:一个线程如果处于空闲时间,并且当前线程数量大于核心线程数时,就会被销毁。默认60s */
private static final int KEEP_ALIVE_TIME = 20;

private ThreadFactory springThreadFactory = new CustomizableThreadFactory("XA-thread-");


private ThreadPoolExecutor executor =
new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUNPOOL_SIZE, KEEP_ALIVE_TIME,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5), springThreadFactory);

private static SingletionThredPool instance = null;

private SingletionThredPool(){};

public void execute(Runnable runnable){
executor.execute(runnable);
}

public void shutdown(){
executor.shutdown();
}

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

}



public class SingletionThredPoolTest implements Runnable{

private SingletionThredPoolTest(){

}

private SingletionThredPoolTest instance = null;

private SingletionThredPoolTest getInstance(){
if(instance == null){
synchronized (SingletionThredPoolTest.class){
if(instance == null){
instance = new SingletionThredPoolTest();
}
}
}
return instance;
}

@Test
public void test() {
long startTime = System.currentTimeMillis();
for (int i=0;i<1000;i++){
SingletionThredPool singletionThredPool = SingletionThredPool.getInstance();
System.out.println("获得的线程实例为:" + singletionThredPool);
SingletionThredPoolTest singletionThredPoolTest = this.getInstance();
System.out.println("当前的测试类为:" + singletionThredPoolTest);
singletionThredPool.execute(singletionThredPoolTest);
}
System.out.println("耗时:" + (System.currentTimeMillis() - startTime));
}

@Override
public void run() {
System.out.println("run: " + Thread.currentThread().getName());
}
}


九、后记

    其实在单例的实现方面还有其他的实现方式,这里仅对较为常用的做一个简单介绍。

    代码已经上传到git:https://github.com/lfgGuang/design-pattern.git

    有一句话这样说,有人一个优秀的船长,你是不是对海域的每一处暗礁都熟知于心,船长说:

        我只需要知道哪些海域是安全的就可以了的。

    更多单例模式的使用和源码剖析会在后续持续更新,更多精彩请持续关注:

    guangmuhua.com


最新评论: