首页 > 编程 > Java > 正文

Java 设计模式 - 单例模式

2019-11-06 07:46:04
字体:
来源:转载
供稿:网友

基本概念

单例模式具有以下特点:

单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。

它的作用如下:

某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。

省去了 new 操作符,降低了系统内存的使用频率,减轻 GC 压力。

有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式。


实例探究

1.饿汉模式

饿汉模式,指在类被加载时(即类对象被调用前)就完成实例化。

这种方式虽然可以保证单例,并了避免多线程安全问题,但是会提前占用系统资源,效率低下。

public class Singleton { // 持有私有静态实例,防止被引用 // 对变量直接赋值,时类在被加载时就被实例化 PRivate static Singleton instance =new Singleton(); // 私有构造方法,防止被实例化 private Singleton() { } public static Singleton getInstance() { return instance; }}

2.懒汉模式

为了解决饿汉模式的效率问题,引入了懒汉模式,该模式指的是在类对象被调用时才创建实例。

虽然懒汉模式解决了效率问题,但又带来线程安全的问题。

当多线程同时调用 getInstance 方法时,都认为 instance 为空,结果就是都新建了类对象。无法保证单例。

public class Singleton { // 不对变量直接赋值,实现延迟加载 private static Singleton instance; private Singleton() { } public static Singleton getInstance() { // 该变量为空时创建 if (instance == null) { instance = new Singleton(); } return instance; }}

2.懒汉模式 - synchronized

普通的懒汉模式既然存在线程安全问题,使用同步关键字 synchronized 就方法加锁能解决。

线程安全的问题是解决了,但是又带来了新的效率问题。

当多线程调用 getInstance 方法,无论对象是否已被创建,它都会被锁住。

public class Singleton { private static Singleton instance; private Singleton() { } // 对方法加锁 public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }}

3.懒汉模式 - 双重检查锁定

当多线程调用 getInstance 方法,如果对方法加锁,就会造成效率问题。

而加锁的主要目的是为了保证创建对象时的线程安全。那么可以缩小同步区域,在新建对象时对其加锁,存在对象时则不需要加锁。

public class Singleton { private static Singleton instance; private Singleton() { } public static synchronized Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { // 之所以要再次检查,因为当线程排序进入同步块时。 // 第一个线程创建了实例,而后面进入同步块的线程就必须立即返回 if(instance == null){ instance = new Singleton(); } } } return instance; }}

5.懒汉模式 - volatile

采用双重检查锁定,貌似是解决了已经问题了。但其实不然。原因就于新建类实例时的操作不是原子性的。

// 该语句实际包含了三条指令:// 1.给 Singleton 实例分配内存。// 2.初始化 Singleton 构造器 // 3.将instance 对象指向分配的内存空间(注意到这步 instance 就非null了)。 instance = new Singleton();

java 内存模型中,允许编译器和处理器对指令进行重排序,但是它会保证程序最终结果会和代码顺序执行结果相同。

所以执行结果可以是 1->2->3,也可是 1->3->2。这种重排序在单线程并不会对线程造成影响,但是在多线程是就会出现问题:

线程1、2 同时访问同步代码块。线程1 线程先进入,线程2 则等待。线程1 创建实例后离开(此时指令执行为 1->3->2)。线程2 进入判断实例不为空,但是线程对于的 Singleton 初始化构造器还没完成,导致程序错误。

而利用 volatile 修饰变量可以保证它的可见性、有序性,从而让执行顺序为 1->2->3,避免了上述问题。

public class Singleton { // 利用 volatile 关键字修饰变量 private volatile static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if(instance == null){ instance = new Singleton(); } } } return instance; }}

6.懒汉模式 - 静态内部类

重新分析以上单例模式会带来的问题:

使用饿汉模式会造成对象提前实例化,占用系统资源。使用懒汉模式会解决了系统资源占用,但会造成线程安全问题。使用 synchronized 、volatile 解决了线程安全问题,但这样实现的效率其实也不高。

所以需要解决的问题有三:系统资源占用、线程安全、执行效率。

因此这里使用静态内部类的方式实现,因为:

不提前占用系统资源:当调用 getInstance 时会访问到 SingletonHolder.instance,此时内部类才被加载并初始化静态变量,创建实例。

不存在线程安全问题:JVM 内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当第一次调用 getInstance 时,JVM能够保证 instance 只被创建一次,并且会保证把赋值给 instance 的内存初始化完毕。

解决效率问题:只有在 getInstance 第一次被调用时,由 JVM 实现互斥,这样就解决了低性能问题。

public class Singleton { // 创建静态内部类 private static class SingletonHolder{ private static Singleton instance= new Singleton(); } private Singleton() { } public static Singleton getInstance() { return SingletonHolder.instance; }}

参考

http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.htmlhttp://blog.csdn.net/jason0539/article/details/23297037/
上一篇:Java泛型

下一篇:Java---IO流内存流

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表