1.什么是单例模式?

《Head First 设计模式》中给出如下定义:确保一个类只有一个实例,并提供一个全局访问点。

关键词:唯一实例对象。

2.单例模式的实现方式:

2.1 懒汉式

对于实例做懒加载处理,即在客户第一次使用时再做创建,所以第一次获取实例的效率会稍微低一些。

 /**
* 懒汉式
* @author Lsj
*/
public class LazySingleton { private static LazySingleton instance; /**
* 获取单一实例对象—非同步方法
* @return
*/
public static LazySingleton getInstance(){
if(instance == null){
try {
TimeUnit.NANOSECONDS.sleep(1);//为了使模拟效果更直观,这里延时1ms,具体看时序图
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
instance = new LazySingleton();
}
return instance;
} }

这种创建方式可以延迟加载、但是在多线程环境下获取到的实例可能并非唯一的,具体见如下验证:

 import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; /**
* 懒汉式
* @author Lsj
*/
public class LazySingleton { private static LazySingleton instance; /**
* 获取单一实例对象—非同步方法
* @return
*/
public static LazySingleton getInstance(){
if(instance == null){
try {
TimeUnit.NANOSECONDS.sleep(1);//为了使模拟效果更直观,这里延时1ms,具体看时序图
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
instance = new LazySingleton();
}
return instance;
} /**
* 增加同步锁
* 避免多线程环境下并发产生多个实例可能的同时,会带来性能上的损耗。
* 事实上只有第一次创建时需要这么做,但后续依然通过加锁获取单例对象就有点因小失大了。
* @return
*/
public synchronized static LazySingleton getInstanceSyn(){
if(instance == null){
try {
TimeUnit.MILLISECONDS.sleep(1);//为了使模拟效果更直观,这里延时1ms,具体看时序图
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
instance = new LazySingleton();
}
return instance;
} public static void main(String[] args) throws InterruptedException {
//模拟下多线程环境下实例可能不唯一的情况
CountDownLatch startSignal = new CountDownLatch(1);
for(int i=0;i<2;i++){//模拟2个线程
Thread t = new Thread(new MyThread(startSignal));
t.setName("thread " + i);
t.start();
}
Thread.sleep(1000);
startSignal.countDown();
} } class MyThread implements Runnable { private final CountDownLatch startSignal; public MyThread(CountDownLatch startSignal){
this.startSignal = startSignal;
} public void run() {
try {
System.out.println("current thread : " + Thread.currentThread().getName() + " is waiting.");
startSignal.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
LazySingleton l = LazySingleton.getInstance();
System.out.println(l);
} }

从验证结果可以看出两个线程同时获取实例时,得到的并非同一个实例对象:

2.2 懒汉式(+同步锁)

 public synchronized static LazySingleton getInstanceSyn(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}

在上述懒汉式的获取对象方法上做出一些改变,给获取实例的方法加上synchronized同步锁, 但是这种做法在多次调用获取实例方法的情况下会带来性能上的损耗。

事实上只有第一次创建实例时需要这么做,但后续依然通过加锁获取单例对象就有点因小失大了。

2.3 饿汉式

顾名思义、在类加载时就将唯一实例一并加载,后续只需要获取就可以了。

 /**
* 饿汉式
* @author Lsj
*/
public class HungrySingleton { private static final String CLASS_NAME = "HungrySingleton"; private static final HungrySingleton instance = new HungrySingleton(); static{
System.out.println("类加载时创建:"+instance);//这里可以看到类加载后,优先加载上方的静态成员变量
} private HungrySingleton(){ } public static HungrySingleton getInstance(){
return instance;
} public static void main(String[] args) throws ClassNotFoundException {
System.out.println(HungrySingleton.CLASS_NAME);//可以看到,这里仅仅是打印HungrySingleton的静态常量,但实例依然被初始化了。
System.out.println("==========分割线==========");
HungrySingleton instance1 = HungrySingleton.getInstance();
System.out.println(instance1);
HungrySingleton instance2 = HungrySingleton.getInstance();
System.out.println(instance2);
} }

运行结果:

从结果可以看到,这种获取单例的方式是线程安全的,JVM保障在多线程情况下一定先创建此实例并且只做一次实例化处理,但是这种情况没有做到懒加载,比如只是引用此类中的一个静态成员变量(常量),此实例在类加载时也一起被初始化了,如果后续应用中不使用这个对象,则会造成资源浪费,占用内存。

2.4 双重检查加锁

此方式可以看做是在懒汉式(+同步锁)方式上的进一步提升,从代码上可以看出主要是针对创建的过程加同步锁。

 /**
* 通过双重检查的方式创建及获取单例对象
* @author Lsj
*/
public class DoubleCheckedLockingSingleton { private volatile static DoubleCheckedLockingSingleton instance; private DoubleCheckedLockingSingleton(){} public static DoubleCheckedLockingSingleton getInstance(){
if(instance == null){
synchronized (DoubleCheckedLockingSingleton.class) {
if(instance == null){
instance = new DoubleCheckedLockingSingleton();
}
}
}
return instance;
} }

这种方式可以大大减少2.2方法中获取实例时不必要的同步操作,需要注意的是:静态成员变量中定义的volatile关键字,保证线程间变量的可见性以及防止指令重排序,同时需要的注意必须是jdk1.5及以上版本(volatile关键字在1.5做出了增强处理)。

--这里后续补充不加volatile关键字的危害。

2.5 静态内部类

此方案是基于类初始化的解决方案,JVM在类的初始化阶段,会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。

 /**
* 以静态内部类的方式来创建获取单例对象
* @author Lsj
*
*/
public class InnerSingleton { private InnerSingleton(){
} public static InnerSingleton getInstance(){
return InnerSingleton.InnerClass.instance;
} static class InnerClass {
private static InnerSingleton instance = new InnerSingleton();
} }

这种方式受益于JVM在多线程环境下对于类初始化的同步控制,这里不再做太详细的说明。

2.6 通过枚举实现单例

《Effective Java》一书中作者Joshua Bloch提倡使用这种方式,这种方式依然是依靠JVM保障,而且可以防止反序列化的时候创建新的对象。

 /**
* 通过枚举实现单例模式
* @author Lsj
*
*/
public class EnumSingleton { public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
} enum Singleton {
INSTANCE;
private EnumSingleton instance; private Singleton(){
instance = new EnumSingleton();
} private EnumSingleton getInstance(){
return instance;
}
} }

笔者没有这么使用过,暂不做过多描述、实际工作中也很少看到有这么用的,待后续有深入了解后再补充。

3. 总结:

几种单例模式的实现方式中,建议使用4,5,6这三种方式,实际根据使用场景作出选择,另外对于上述提到的几种方式1~5,需要防范通过反射或反序列化的手段创建对象从而使得实例不再唯一,笔者也会在后续会对此作出补充。

参考文献:

《Head Frist设计模式》

《Effective Java》

《Java并发编程的艺术》

JAVA设计模式之单例模式(单件模式)—Singleton Pattern的更多相关文章

  1. 设计模式 - 单件模式(singleton pattern) 具体解释

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u012515223/article/details/28595349 单件模式(singleton ...

  2. 设计模式----创建型型模式之单件模式(Singleton pattern)

    单件模式,又称单例模式,确保一个类只有一个实例,并提供全局访问点. 单件模式是比较简单且容易理解的一种设计模式.只有一个实例,通常的做法...TODO 类图比较简单,如下所示: 示例代码: 懒汉模式( ...

  3. C#设计模式——单件模式(Singleton Pattern)

    一.概述在软件开发过程中,我们有时候需要保证一个类仅有一个实例,比如在一个电脑用户下只能运行一个outlook实例.这时就需要用到单件模式.二.单件模式单件模式保证一个类仅有一个实例,并提供一个访问它 ...

  4. 【java设计模式】【创建模式Creational Pattern】单例模式Singleton Pattern

    //饿汉式:资源利用率较低(无论是否需要都会创建),性能较高(使用前无需判断实例是否存在,可直接使用) public class EagerSingleton{ private static fina ...

  5. Java 设计模式(四)-工厂方法模式 (FactoryMethod Pattern)

    1     概念定义 1.1   定义 定义一个用于创建对象的接口,让子类决定实例化哪一个类.工厂方法使一个类的实例化延迟到其子类. 1.2   类型 创建类模式 2     原理特征 2.1   类 ...

  6. 【java设计模式】【结构模式Structural Pattern】合成模式Composite Pattern

    package com.tn.pattern; import java.util.Vector; public class Client { public static void main(Strin ...

  7. 1.单件模式(Singleton Pattern)

    意图:为了保证一个类仅有一个实例,并提供一个访问它的全局访问点. 1.简单实现(多线程有可能产生多个实例) public class CommonSigleton { /// <summary& ...

  8. 【java设计模式】【创建模式Creational Pattern】建造模式Builder Pattern

    package com.tn.pattern; public class Client { public static void main(String[] args) { Director dire ...

  9. 【java设计模式】【创建模式Creational Pattern】抽象工厂模式Abstract Factory Pattern

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAW0AAABvCAIAAACo3AbKAAALvUlEQVR4nO1dUa7cOA7U/c+zwJxkf4

  10. 【java设计模式】【行为模式Behavioral Pattern】模板方法模式Template Method Pattern

    package com.tn.pattern; public class Client { public static void main(String[] args) { AbstractClass ...

随机推荐

  1. Docker学习笔记——1.2 Docker组件

    Docker的核心组件包括: Docker客户端和服务器,也称为Docker引擎: Docker镜像: Registry: Docker容器. 1.Docker客户端和服务器 Docker是一个客户端 ...

  2. C# 获取或设置指定 config 文件的值

    ExeConfigurationFileMap 这个类提供了修改.获取指定 config 的功能:新建一个 ExeConfigurationFileMap 的实例 ecf :并设置 ExeConfig ...

  3. Keystone

    Kenstone各个概念的比喻: User 住宾馆的人 Credentials 开启房间的钥匙 Authentication 宾馆为了拒绝不必要的人进出宾馆,专门设置的机制,只有拥有钥匙的人才能进出 ...

  4. 我的Java之路

    前言: 之前在学习python,刚开始的时候跟多数小白一样学习一些基础的知识,比如数据类型,用法,基本的语言结构,学了一段时间实在是学习不下去了,真是太TMD的无聊了,很多方法都记不住,也不知道学了这 ...

  5. 【GStreamer开发】GStreamer播放教程03——pipeline的快捷访问

    目的 <GStreamer08--pipeline的快捷访问>展示了一个应用如何用appsrc和appsink这两个特殊的element在pipeline中手动输入/提取数据.playbi ...

  6. 在Jetty中部署Jenkins遇到的问题

    1. Jetty 9.0.3 启动时的错误: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@kvm-guest jetty-9.0.3]# java -jar star ...

  7. ubuntu安装mysql遇到的坑----解决Mysql报错缺少libaio.so.1

    最近学习大数据,涉及到hive的部分需要安装mysql,于是就在linux环境下尝试安装,对于我这个linux小白来说,中间遇到很多坑爹问题,在这里做一个记录. 我参考的mysql安装博客: http ...

  8. LeetCode 112. 路径总和(Path Sum) 10

    112. 路径总和 112. Path Sum 题目描述 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和. 说明: 叶子节点是指没有子节点的节 ...

  9. jar 常用操作

    查看 jar 包中的文件列表,并进行重定向 jar -tvf a.jar > a.txt 更新文件到 jar 中,目录需对应 jar -uf a.jar com/a.class a.class ...

  10. css特效实现透明渐变

    知乎发现栏目上的标题图一般都是以下图方式展现的,很显然它是利用渐变去实现的.思路很有意思,主要是要有两方面的认知: 这张图其实可以分成两部分,右边控制图形和渐变,左边就是一张纯色背景,和渐变无关 透明 ...