您的位置:首页 > 编程语言 > Java开发

JAVA单例模式分析及其实现:饿汉式、懒汉式、双重检查、静态内部类

2020-03-06 16:42 1516 查看

本文是对站中学院视频单例模式的学习笔记。视频在此

直入正题

单例模式是什么

首先,单例模式是设计模式的一种,是对类的编写的一种模式规范。具体到单例模式,

通俗说,例就是实例,就是对象;单就是一,代表唯一。所以说,单例模式,就是该类仅能有一个实例化对象的设计模式。

较全面的说,单例模式即使保证在整个应用程序的生命周期中的任意时刻,单例模式类的实例都只存在不超过一个(一个或者不存在)。

单例模式实现基础

知道了单例模式的基本含义,接着需要探讨实现单例类的基础,也就是怎样的设计可以实现这样的模式。

可参考如下对单例类的描述:单例模式在类中保存了它唯一的实例,同时它还提供了一个访问该唯一实例的全局方法。

在该描述中,我们获得了以下要点:

  • 单例类在内部保留了它唯一的实例
  • 单例类向外界提供了访问这个唯一实例的全局方法

就这两点方法,我们便可以展开分析(事实上,下文不同的写法也正是围绕着这两点进行修正。):

内部保留唯一实例

这个要点实际上需要完成两点:
1.外部不能进行实例化。
2.内部需要实例化一个私有对象

第一点,外部不能对该类进行实例化,是单例类唯一实例的根本保证。那么让外界不能对一个类进行实例化,可以采取的一种设计是让外界在进行实例化时无法调用构造方法,即将构造方法定义为私有。这样一来外部便不能在new后调用与类同名的构造方法产生实例。消除外部实例化可能性,完成!

第二点,单例类内部需要有一个实例化对象。对于这个对象,由于这个对象被类所独有,所以对象访问级别为私有;由于该类不能在外部产生对象,所以作为成员的这个对象不能是一般的成员变量,它必须为静态变量

综上,解决这两点问题,需要
1.单例类的构造方法需要为私有
2.单例类内需要包含一个该类的静态私有变量

(对于静态内部类的写法,可将成员变量放入内部静态类中)

提供全局方法获得实例

这里,同样是由于该类不能被外部实例化对象,所以刚方法仅能通过类直接访问。所以,该方法应该是静态全局的,同时返回类型为该单例类类型
(笔者个人理解:由于涉及到线程安全和多线程下的效率问题,不同的实现方法主要就在这获得实例上斗智斗勇。)

实现单例模式

单例类的实现方法有很多。这里,仅列举六种写法:

1.饿汉式写法,静态成员变量

/*单例模式,饿汉式写法1-静态常量*/

public class Singleton1 {
private static Singleton1 uniqueInstance = new Singleton1();

private Singleton1(){}

public static Singleton1 getInstance()
{
return uniqueInstance;
}
}

最基本的写法,在定义静态常量同时完成实例化,获得对象方法直接返回对象。
该写法不用考虑线程的问题,但是缺点在于它在类进行加载时便进行了实例化,会造成内存浪费。

2.饿汉式写法,静态代码块

/*单例模式,饿汉式写法2-静态代码块*/

public class Singleton2 {
private Singleton2(){};
private static Singleton2 uniqueInstance;

static
{
uniqueInstance = new Singleton2();
}

public static Singleton2 getInstance(){
return uniqueInstance;
}
}

由于装载类时,在初始化顺序上静态代码块紧随静态变量其后,所以该写法同上一个写法异曲同工,相当于将定义同初始化分开写的静态版本,本质上没有差别,故优缺点同上。

3.懒汉式写法,线程不安全型

/*单例模式,懒汉式写法,懒加载线程不安全型*/

public class Singleton3 {
private Singleton3(){}
private static Singleton3 uniqueInstance;
public static Singleton3 getInstance()
{
if(uniqueInstance == null)
{
uniqueInstance = new Singleton3();
}
return uniqueInstance;
}
}

为了针对上一个饿汉式写法中过早实例化占用内存的问题,可利用这种写法,在获得对象时临时判断是否初始化过对象,如果没有则现实例化一个对象。(果然如其名,有懒汉临阵磨枪的感觉)。但是这样的写法有一个致命的问题,那就是它是线程不安全的。如果有一个线程停滞在了if语句那里,在另一个线程经过if语句完成实例化后,这个线程会继续实例化一个新的对象,这样就不能保证实例唯一。

4.懒汉式写法,线程安全

/*单例模式,懒汉式写法,懒加载线程安全*/

public class Singleton4 {
private Singleton4(){}
private static Singleton4 uniqueInstance;
public static synchronized Singleton4 getInstance()
{
if(uniqueInstance == null)
{
uniqueInstance = new Singleton4();
}
return uniqueInstance;
}
}

针对上一个写法的线程不安全问题,可以选择在获得对象方法前添加synchronized来实现该方法的线程同步,从而保证线程安全
但是这样的写法仍旧存在缺点,那就是在线程同步的过程中会造成不小的延迟。所以,这样的写法仍有改进的余地。

5.懒汉式写法,双重检查

/*单例模式,双重检查,懒加载高效线程安全*/

public class Singleton5 {

private static volatile Singleton5 uniqueInstance;

private Singleton5(){}

public static Singleton5 getInstance()
{
if(uniqueInstance == null)
{
synchronized(Singleton5.class){
if(uniqueInstance == null)
{
uniqueInstance = new Singleton5();
}
}
}

return uniqueInstance;
}
}

这里对上一个写法进行升级。为了减少线程同步带来的延迟,提高效率,将同步从对方法转为对类,加入两重判断防止重复进入,同时将对象加上volatile关键字修饰(volatile关键字作用,可参考:博文1博文2。可以粗浅的的理解为一个轻量的synchronized,在线程中每次操作都对变量的值进行重读)。
该写法同时兼顾了线程安全和懒加载,是实际应用中的常用推荐写法

6.懒汉式写法,静态内部类

/*单例模式,静态内部类,懒加载高效线程安全*/

public class Singleton6 {

private Singleton6(){}

private static class LazyHolder
{
private static Singleton6 uniqueInstance = new Singleton6();
}

public static Singleton6 getInstance()
{
return LazyHolder.uniqueInstance;
}
}

这个写法的设计十分的巧妙,将对象定义在静态内部类中,由于内部静态类不随类加载且加载时线程安全的特性(可参考博文1博文2)将懒加载和线程安全以及线程同步的效率问题一并解决。同时,获取实例方法也回归了最初的简易逻辑,较上一写法更为简洁,因此也作为实际使用时常用的推荐写法

笔者水平有限,错误不妥之处,欢迎指正。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
wayne_lee_lwc 发布了14 篇原创文章 · 获赞 2 · 访问量 327 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐