【设计模式】Java设计模式之单例模式

目录

一、模式动机与定义

1、模式动机

2、模式定义

二、模式结构与分析

1、模式结构

2、模式分析

三、单例模式八种方式

四、模式的优缺点

1、单例模式的优点

2、单例模式的缺点

五、模式适用的环境

六、单例模式在JDK中的应用


一、模式动机与定义

1、模式动机

在做开发的过程中,会注意到这样的需求,系统中的某个类只需要或只能有一个实例。想要保证一个类只有一个实例且容易被访问到,一个很好地解决方法就是让类自身负责保存它的唯一实例,这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法,这就是“单例模式”。

2、模式定义

单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

Singleton Pattern: Ensure a class has only one instance and provide a global point of access to it.

我们不难发现定义中有三个关键要素

  1. 某个类只能有一个实例

  2. 它必须自行创建这个实例

  3. 它必须自行向整个系统提供这个实例

二、模式结构与分析

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;
} }

三、单例模式八种方式

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块,白给)
  6. 双重检查
  7. 静态内部类
  8. 枚举

单例模式有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、单例模式的缺点

  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;现在很多面向对象语言(JavaC#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失

五、模式适用的环境

  • 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器,或者需要考虑资源消耗太大而只允许创建一个对象。
  • 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
  • 在一个系统中要求一个类只有一个实例时才应当使用单例模式。反过来,如果一个类可以有几个实例共存,就需要对单例模式进行改进,使之成为多例模式。

六、单例模式在JDK中的应用

java.lang.Runtime类,追踪源码就会发现其应用了单例模式。

发布了31 篇原创文章 · 获赞 6 · 访问量 4198

猜你喜欢

转载自blog.csdn.net/zhenliangit0918/article/details/104218778