Java 设计模式 之 单例模式(Singleton)
目录
单例模式(Singleton)
1、单例对象(Singleton)是一种常用的设计模式,在 Java 应用中,单例对象能保证在一个 JVM 中,该对象只有一个实例存在。
2、这样的模式有几个好处:
1)某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2)省去了 new 操作符,降低了系统内存的使用频率,减轻 GC 压力。
3)多线程中进行数据共享,有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
3、需要清楚的是单例模式作为一种模式,其实现方式并不是规定死的,可以是任意的,只要能达到期望的效果即可。也就是说实际情况中可能大家写的各有不同,但是大同小异。
最简单的单例类
1、下面开始写一个简单的单例类:
package main.singleton;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/4/8 0008.
* trade:交易,实现单例模式
*/
public class Trade {
//私有静态实例,防止被引用。此处赋值为 null,目的是实现延迟加载
private static Trade instance;
//私有构造方法,防止被实例化
private Trade() {
}
//静态工程方法,创建实例
public static Trade getInstance() {
if (instance == null) {
instance = new Trade();
Logger.getAnonymousLogger().info("创建 Trade 实例..." + Thread.currentThread().getName());
}
return instance;
}
}
4、测试如下:
package main.test;
import main.singleton.Trade;
/**
* Created by Administrator on 2019/4/8 0008.
*/
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread() {
@Override
public void run() {
System.out.println("--------------" + Thread.currentThread().getName());
Trade.getInstance();
}
}.start();
}
}
}
演示的是 10 个线程同时创建 Trade 实例,结果是只有第一个操作的线程会创建一次对象,后面的不会再创建。
加锁版单例类
1、上面的 Trade 类基本可以满足要求,但是像这样毫无线程安全保护的类,如果把它放入多线程的环境下,仍然有可能会出现问题。首先会想到对 getInstance 方法加 synchronized 关键字。
package main.singleton;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/4/8 0008.
* trade:交易,实现单例模式
*/
public class Trade {
//私有静态实例,防止被引用。此处赋值为 null,目的是实现延迟加载
private static Trade instance;
//私有构造方法,防止被实例化
private Trade() {
}
//静态工程方法,创建实例,添加了类锁
public synchronized static Trade getInstance() {
if (instance == null) {
instance = new Trade();
Logger.getAnonymousLogger().info("创建 Trade 实例..." + Thread.currentThread().getName());
}
return instance;
}
}
2、上面这样添加 synchronized 关键字后,每次调用 getInstance()方法,都要对对象上锁,事实上只有在第一次创建对象的时候需要加锁,之后就不需要了,所以可以改进如下:
package main.singleton;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/4/8 0008.
* trade:交易,实现单例模式
*/
public class Trade {
//私有静态实例,防止被引用。此处赋值为 null,目的是实现延迟加载
private static Trade instance = null;
//私有构造方法,防止被实例化
private Trade() {
}
//静态工程方法,创建实例
public static Trade getInstance() {
if (instance == null) {
/**
* 创建实例时添加类锁
* 这样当第一次 instance 为 null时,添加类锁,后续不再需要加锁
*/
synchronized (Trade.class) {
if (instance == null) {
instance = new Trade();
Logger.getAnonymousLogger().info("创建 Trade 实例..." + Thread.currentThread().getName());
}
}
}
return instance;
}
}
3、因为只需要在创建实例的时候进行同步,所以只要将创建实例和 getInstance() 方法分开,单独为创建实例加 synchronized 关键字也是可以的,如下所示:
package main.singleton;
import java.util.logging.Logger;
/**
* Created by Administrator on 2019/4/8 0008.
* trade:交易,实现单例模式
*/
public class Trade {
//私有静态实例,防止被引用。此处赋值为 null,目的是实现延迟加载
private static Trade instance = null;
//私有构造方法,防止被实例化
private Trade() {
}
//同步创建实例,添加了类锁
private synchronized static void syncInit() {
if (instance == null) {
instance = new Trade();
Logger.getAnonymousLogger().info("创建 Trade 实例..." + Thread.currentThread().getName());
}
}
//静态工程方法,创建实例
public static Trade getInstance() {
if (instance == null) {
syncInit();
}
return instance;
}
}
实现方式并不是唯一的,但都是大同小异,除了使用 synchronized 关键字,比如也可以使用 Lock 接口。
内部类单例类
1、加了 synchronized 关键字,只有在 instance 为 null 时,为其创建对象,同时加锁,性能有一定的提升。但是仍然可能存在问题的。
2、Java 指令中创建对象和赋值操作其实是分开进行的,也就是说 instance = new Trade(); 语句是分两步执行的。而 JVM 并不保证这两个操作的先后顺序,也就是说有可能 JVM 会先为新的 Trade 实例分配空间,然后直接赋值给 instance 成员,然后再去初始化这个 Singleton 实例。这样有可能会出错,以 A、B 两个线程为例:
1)A、B 线程同时进入了第一个 if (instance == null) { 判断
2)A 首先进入 synchronized ,由于 instance 为 null,所以它执行 instance = new Trade();
3)由于 JVM 内部的优化机制,JVM 先划出了一些分配给 Trade 实例的空白内存,并赋值给 instance 成员(注意此时JVM没有开始初始化这个实例),然后 A 离开了synchronized。
4)B 进入 synchronized ,由于 instance 此时不是null,因此它马上离开了synchronized 并将结果返回给调用该方法的程序。
5)此时 B 线程打算使用 Trade 实例,却发现它没有被初始化,于是错误发生了。
3、可以使用内部类的方式解决如下:
package main.singleton;
/**
* Created by Administrator on 2019/4/8 0008.
* trade:交易,实现单例模式
*/
public class Trade {
//私有构造方法,防止被实例化
private Trade() {
}
//此处使用一个内部类来维护单例
//static 修饰的成员变量随着类的加载而加载,在类加载时进行唯一一次的初始化
private static class TradeFactory {
private static Trade instance = new Trade();
}
//静态工程方法,创建实例
public static Trade getInstance() {
return TradeFactory.instance;
}
}
1)单例模式使用内部类来维护单例的实现,JVM 内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的,这样当第一次调用 getInstance 的时候,JVM 能够保证 instance 只被创建一次,并且会保证把赋值给 instance 的内存初始化完毕。
2)该方式也只会在第一次调用的时候使用互斥机制。