【Java设计模式详解】单例模式

序上一篇文章

什么是单例?

用一个词来概括:“唯一”。

它的使用场景?

  • 当类只有一个实例而且客户可以从一个众所周知的访问点访问它时
  • 例如:数据库访问、socket连接

它的基本实现步骤:

  • 构造方法私有,保证无法从外部通过 new 的方式创建对象。
  • 对外提供获取该类实例的静态方法
  • 类的内部创建该类的对象,通过第 2 步的静态方法返回

它的实现方法?

  • 懒汉式
  • 饿汉式
  • 静态内部类

下面我将结合代码来诠释单例干啥用的。
饿汉式:

/**
 * 
 * ProjectName: AaronTest
 * ClassName: Singleton 
 * @Description: 懒汉式(立即加载)
 * @author Aaron
 * @date 2019年7月11日
 */
public class LazymanSingleton1 {
	//私有化构造方法
	private LazymanSingleton1() {
		
	}
	//私有化静态实例对象
	private static LazymanSingleton1 singleton = new LazymanSingleton1();
	//对外提供一个获取该实例的静态方法
	public LazymanSingleton1 getIntance() {
		return singleton;
	}
}

它是加载该类时立即加载里面的实例方法。

饿汉式第二种实现方法:

/**
 * 只能创建惟一的一个实例、线程安全
 * ProjectName: AaronTest
 * ClassName: Singleton1 
 * @Description: 懒汉式(立即加载)
 * @author Aaron
 * @date 2019年7月11日
 */
public class LazymanSingleton {
	//私有化构造方法
	private LazymanSingleton() {
		
	}
	
	private static LazymanSingleton singleton = null;
	//静态代码块
	static {
		singleton = new LazymanSingleton();
	}
	public LazymanSingleton getIntance() {
		return singleton;
	}
}

饿汉式的优点:它在类初始化时就创建好一个对象供外部使用,除非系统重启否则不会变,所以它本身是线程安全的。

懒汉式(延迟加载)

/**
 *    多线程时会产生多个单例  ,线程不安全(使用双重检索解决)
 * ProjectName: AaronTest
 * ClassName: HungrySingleton 
 * @Description: 饿汉式(延迟加载)
 * @author Aaron
 * @date 2019年7月11日
 */
public class LazymanSingleton {
	private LazymanSingleton() {
		
	}
	private static LazymanSingleton singleton = null;
	//同步锁
	 public static LazymanSingleton getInstance() {
		 //双重检索
		if (singleton==null) {
			synchronized (LazymanSingleton.class) {
				if (singleton==null) {
					singleton = new LazymanSingleton();
				}
			}
		} 
			return singleton;
	}
}

它虽然使用延迟加载实现了懒汉式单例,但如果在多个线程下就回产生多个单例对象。这违反了单例模式的原则。于是可以通过synchronized 关键词+双重检索来解决该问题。只对需要锁的部分代码加锁,提高了执行效率。

当我们都认为这一切的看上去很完美的时候,JVM 又给我提出了个难题,那就是指令重排。

什么是指令重排,指令重排的用大白话来简单的描述,就是说在我们的代码运行时,JVM 并不一定总是按照我们想让它按照编码顺序去执行我们所想象的语义,它会在 “不改变” 原有代码语句含义的前提下进行代码,指令的重排序。

那么我们上述双重检验锁的单例实现问题主要出在哪里呢?问题出在 singleTon = new SyncSingleTon();这句话在执行的过程。首先应该进行对象的创建操作大体可以分为三步:

(1)分配内存空间。

(2)初始化对象即执行构造方法。

(3)设置 Instance 引用指向该内存空间。
 
那么如果有指令重排的前提下,这三部的执行顺序将有可能发生变化:
 
(1)分配内存空间。
 
(2)设置 Instance 引用指向该内存空间。
 
(3)初始化对象即执行构造方法。

如果按照上述的语义去执行,单看线程 A 中的操作虽然指令重排了,但是返回结果并不影响。但是这样造成的问题也显而易见,b 线程将返回一个空的 Instance,可怕的是我们认为这一切是正常执行的.
为了解决上述问题我们可以使用静态内部类实现,也就是下面即将讲解的一种方法。当然通过“Volatile”也是可以实现的。但我们这里重点是讲单例。所以“Volatile”就不做详解。

静态内部类

/**
 *    
 * ProjectName: AaronTest
 * ClassName: HungrySingleton 
 * @Description: 静态内部类
 * @author Aaron
 * @date 2019年7月11日
 */
public class StaticSingleton {
	private StaticSingleton() {
		
	}
	//外部类被加载,内部类没有被加载。
	private static class InSideClass {
		private static StaticSingleton singleton = new StaticSingleton();
	}
	public StaticSingleton getInstance() {
		return InSideClass.singleton;
	}
}

通过上述的代码实现,我相信你们也大概知晓了单例是干啥用的了吧!
好了单例就讲解到这,后续我会讲解设计模式中的另一种“代理模式”

你可能感兴趣的:(设计模式)