单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。
在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
提供了对唯一实例的受控访问。
由于在系统内存中只存在一个对象,因此可以 节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
允许可变数目的实例。
避免对共享资源的多重占用。
示例代码
package com.zhaoyanfei.designpattern.signleton;
/**
* 单例模式
* @author zhaoYanFei
*
*/
public class Singleton {
private static Singleton instance;
/**
* 私有构造器
*/
private Singleton() {
System.out.println("Singleton is Instantiated.");
}
/**
* 全局静态方法访问实例
* @return
*/
public static Singleton getInstance() {
if(instance == null)
instance = new Singleton();
return instance;
}
public void doSomething() {
System.out.println("Something is done.");
}
}
接下来,当我们在代码中使用单例对象时,只需进行简单调用,示例如下:
package com.zhaoyanfei.designpattern.signleton;
public class Test {
public static void main(String[] args) {
//使用单例对象执行相应的操作
Singleton.getInstance().doSomething();
}
}
同步锁单例模式
(1)单例模式的实现代码简单且高效,但是还需要注意一种特殊情况,那就是多线程应用中,可能存在两个线程同时调用 getInstance 方法的情况。
比如:第一个线程首先使用构造器实例化单例对象,这时,第二个线程进来,在第一个线程还完成单例对象的实例化操作的情况下,也会开始实例化单例对象。
当然,上述场景发生的概率很小,但是在实例化单例对象需要较长时间的情况下,发生的可能性就足够高了,所以,我们不能忽略这种并发操作。
解决这个问题也很简单,只需要用关键字(synchronized)来保证线程安全。有两种方式:
获取实例的方法 getInstance() 上添加 synchronized 关键字来保证线程安全。
public static synchronized Singleton getInstance() {
if(instance == null)
instance = new Singleton();
return instance;
}public static Singleton getInstance() {
synchronized(Singleton.class) {
if(instance == null)
instance = new Singleton();
}
return instance;
}
用 synchronized 代码块包装 if(instance==null) 条件,这里需要指定一个对象来提供锁,Singleton.class 对象就起这种作用。
public static Singleton getInstance() {
synchronized(Singleton.class) {
if(instance == null)
instance = new Singleton();
}
return instance;
}
双重检验锁机制的同步锁单例模式
/**
* 全局静态方法访问实例
* @return
*/
public static Singleton getInstance() {
//1.先判断是否有实例
if(instance == null) {
synchronized(Singleton.class) {
//2.同步代码块内再次检查是否有实例
if(instance == null)
instance = new Singleton();
}
}
return instance;
}
这里我们看到判断了两次实例是否存在,因为我们需要保证在 synchronized 代码块中也要进行一次检查。
无锁的线程安全单例模式
Java 中单例模式的最佳实现形式中,类只会加载一次,通过在声明时直接实例化静态成员属性的方式来保证一个类只有一个实例。
这种实现方式避免了使用同步锁机制和判断实例是否被创建的额外检查。
package com.zhaoyanfei.designpattern.signleton;
/**
* 无锁线程安全单例模式
* @author zhaoYanFei
*
*/
public class LockFreeSingleton {
/**
* 实例化静态成员属性
*/
private static final LockFreeSingleton instance = new LockFreeSingleton();
private LockFreeSingleton() {
System.out.println("LockFreeSingleton is Instantiated.");
}
public static synchronized LockFreeSingleton getInstance() {
return instance;
}
public void doSomething() {
System.out.println("Something is done.");
}
}
提前加载和延迟加载
提前加载和延迟加载的区别,就是实例对象被创建的时机。
如果在应用开始时创建单例实例,那就是提前加载单例模式。
如果在 getInstance 方法首次被调用时才调用单例构造器,那就是延迟加载单例模式。
前面的无锁线程安全单例模式在早期版本的 Java 中被认为是提前加载单例模式,但在最新版本的 Java 中,类只有在使用时才会被加载,所以它也是一种延迟加载模式。
另外,类加载的时机主要取决于 JVM 的实现机制,因而版本之间会有所不同。
目前,Java 语言并没有提供一种创建提前加载单例模式的可靠选项。如果确实需要提前实例化,可以在程序的开始通过调用 getInstance() 方法强制执行,如下面代码所示:
Singleton.getInstance();