Java设计模式之单例模式
目录
一、模式动机与定义
1、模式动机
在做开发的过程中,会注意到这样的需求,系统中的某个类只需要或只能有一个实例。想要保证一个类只有一个实例且容易被访问到,一个很好地解决方法就是让类自身负责保存它的唯一实例,这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法,这就是“单例模式”。
2、模式定义
单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
Singleton Pattern: Ensure a class has only one instance and provide a global point of access to it.
我们不难发现定义中有三个关键要素:
-
某个类只能有一个实例;
-
它必须自行创建这个实例;
-
它必须自行向整个系统提供这个实例。
二、模式结构与分析
1、模式结构
2、模式分析
单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。
代码实现如下:
public class Singleton
{
private static Singleton instance=null; //静态私有成员变量
//私有构造函数
private Singleton()
{}
//静态公有工厂方法,返回唯一实例
public static Singleton getInstance()
{
if(instance==null)
instance=new Singleton();
return instance;
} }
三、单例模式八种方式
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块,白给)
- 双重检查
- 静态内部类
- 枚举
单例模式有8种形式写法,接下来依次对其进行分析。
1、饿汉式(静态常量)
package singleton.singletontest1;
/**
* 饿汉式(静态常量)应用实例
* @author jinzhenliang
*
*/
public class Singletontest1 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
System.out.println("instance1.hashcode="+instance1.hashCode());
System.out.println("instance2.hashcode="+instance2.hashCode());
}
/*
* result:
* true
instance1.hashcode=26253138
instance2.hashcode=26253138
*/
/*
* 优缺点——
* 优点:这种写法比较简单,就是在类装载的时候也就完成实例化,避免了线程同步问题
* 缺点:在类装载的时候就完成实例化,没有达到lazyLoading的效果。如果从始至终未使用过这个实例,则会造成内存的浪费。
* 这种方式基于classloder机制避免了多线程同步问题,不过instance在类装载的时候就实例化,在单利模式中大多数都是电泳getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或其他的静态方法)导致类装载,这时候初始化instance就没有达到lazyloading的效果
* 结论:这种单例模式可用,可能会造成内存浪费
*/
}
class Singleton{
//1、一个私有的构造器(放置通过new来创建对象)
private Singleton() {
}
//2、类的内部创建对象
private final static Singleton instance = new Singleton();
//3、向外暴露一个静态的公共方法
public static Singleton getInstance() {
return instance;
}
}
2、饿汉式(静态代码块)
package singleton.singletontest2;
/**
* 饿汉式(静态代码块)应用实例
* @author jinzhenliang
*
*/
public class Singletontest2 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
System.out.println("instance1.hashcode="+instance1.hashCode());
System.out.println("instance2.hashcode="+instance2.hashCode());
}
/*
* result:
* true
instance1.hashcode=26253138
instance2.hashcode=26253138
*/
/*
* 优缺点——
* 优缺点和在静态变量是一致的,只不过将类实例化的过程方再龙
* 静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化累的实例
* 结论:这种单例模式可用,可能会造成内存浪费
*/
}
class Singleton{
//1、一个私有的构造器(放置通过new来创建对象)
private Singleton() {
}
//2、类的内部创建对象
private static Singleton instance;
static {
//3、在静态代码块中创建实例对象
instance=new Singleton();
}
//4、向外暴露一个静态的公共方法
public static Singleton getInstance() {
return instance;
}
}
3、懒汉式(线程不安全)
package singleton.singletontest3;
/**
* 懒汉式(线程不安全的)
* @author jinzhenliang
*
*/
public class Singletontest3 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
System.out.println("instance1.hashcode="+instance1.hashCode());
System.out.println("instance2.hashcode="+instance2.hashCode());
}
/*
* result
* true
instance1.hashcode=26253138
instance2.hashcode=26253138
*/
/*
* 优缺点:
* 起到了懒加载的效果,但是只能在单线程下使用
* 如果在多线程下,一个线程进入了if(instance==null)判断语句,还未来得及、
* 往下执行,另一个线程也通过了这个判断语句,这时会产生多个实例。
* 所以在多线程环境下不可用使用这种方式。
* 结论:在实际开发中,不适用这种方式
*/
}
class Singleton{
private static Singleton instance;
private Singleton() {
}
//提供一个静态的公有方法,当使用该方法时。才去创建instance,即懒汉式
public static Singleton getInstance() {
if(instance==null) {
instance = new Singleton();
}
return instance;
}
}
4、懒汉式(线程安全,同步方法)
package singleton.singletontest4;
/**
* 懒汉式(线程安全,同步方法)
* @author jinzhenliang
*
*/
public class Singletontest4 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
System.out.println("instance1.hashcode="+instance1.hashCode());
System.out.println("instance2.hashcode="+instance2.hashCode());
}
/*
* result
* true
instance1.hashcode=26253138
instance2.hashcode=26253138
*/
/*
* 优缺点:
* 解决了线程不安全问题
* 效率太低,每个线程在想获得类的实例的时候,执行getInstance()方法
* 都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例。
* 直接return就行了。方法进行同步效率太低
* 结论:在实际开发中,不推荐用这种方式
*/
}
class Singleton{
private static Singleton instance;
private Singleton() {
}
//提供一个静态的公有方法,加入了同步处理的代码,解决线程安全问题,当使用该方法时。才去创建instance,即懒汉式
public static synchronized Singleton getInstance() {
if(instance==null) {
instance = new Singleton();
}
return instance;
}
}
5、懒汉式(线程安全,同步代码块,白给)
package singleton.singletontest5;
/**
* 懒汉式(线程安全,同步代码块)白给
* @author jinzhenliang
*
*/
public class Singletontest5 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
System.out.println("instance1.hashcode="+instance1.hashCode());
System.out.println("instance2.hashcode="+instance2.hashCode());
}
/*
* result
* true
instance1.hashcode=26253138
instance2.hashcode=26253138
*/
/*
* 实则白给,没有解决线程安全问题,效率还低
* 不可以使用
*/
}
class Singleton{
private static Singleton instance;
private Singleton() {
}
//提供一个静态的公有方法
public static Singleton getInstance() {
if(instance==null) {
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
}
6、双重检查
package singleton.singletontest6;
/**
*双重检查
*Double-check概念是多线程开发中常使用的,我们进行了两次
*if(instance==null)的检查,这样就保证线程安全
*实例化代码只执行一次,后面再次访问时,可避免反复进行方法同步
*线程安全;延迟加载,效率较高
*在实际开发中,推荐使用这种单例设计模式
* @author jinzhenliang
*
*/
public class Singletontest6 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
System.out.println("instance1.hashcode="+instance1.hashCode());
System.out.println("instance2.hashcode="+instance2.hashCode());
}
}
class Singleton{
//volatile防止指令重排
private static volatile Singleton instance;
private Singleton() {
}
//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题
public static Singleton getInstance() {
if(instance==null) {
synchronized(Singleton.class) {
if(instance==null) {
instance = new Singleton();
}
}
}
return instance;
}
}
7、静态内部类
package singleton.singletontest7;
import com.sun.swing.internal.plaf.synth.resources.synth;
/**
* 静态内部类
* @author jinzhenliang
*
*/
public class Singletontest7 {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
System.out.println("instance1.hashcode="+instance1.hashCode());
System.out.println("instance2.hashcode="+instance2.hashCode());
}
/*
* result
* true
instance1.hashcode=26253138
instance2.hashcode=26253138
*/
/*
* 这种方式采用了类装载的机制来保证初始化实例时只有一个线程
* 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时。
* 调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化
* 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们
* 保证了线程的安全性,在类进行初始化的时候,别的线程是无法进入的
* 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
* 推荐使用
*/
}
class Singleton{
private static volatile Singleton instance;
private Singleton() {
}
private static class SingletonInstance{
private static final Singleton instance=new Singleton();
}
public static synchronized Singleton getInstance() {
return SingletonInstance.instance;
}
}
8、枚举
package singleton.singletontest8;
/**
* 枚举方式
* @author jinzhenliang
*
*/
public class Singletontest8 {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1==instance2);
System.out.println("instance1.hashcode="+instance1.hashCode());
System.out.println("instance2.hashcode="+instance2.hashCode());
}
/*
* 借助JDK1.5添加的枚举来实现单例模式,不仅能够避免多线程同步问题,而且还能防止反序列化重新创建新的对象
* 这种方式推荐使用
*/
/*
* 单例模式在JDK中的应用
* Runtime
*/
}
enum Singleton{
INSTANCE;
public void sayOK() {
System.out.println("###ok");
}
}
四、模式的优缺点
1、单例模式的优点
- 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它,并为设计及开发团队提供了共享的概念。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。我们可以基于单例模式进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例。
2、单例模式的缺点
- 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失
五、模式适用的环境
- 系统只需要一个实例对象,如系统要求提供一个唯一的***生成器,或者需要考虑资源消耗太大而只允许创建一个对象。
- 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
- 在一个系统中要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就需要对单例模式进行改进,使之成为多例模式。
六、单例模式在JDK中的应用
java.lang.Runtime类,追踪源码就会发现其应用了单例模式。
上一篇: Spring-Boot原理分析
下一篇: Spring Boot自动配置原理分析