单例模式看似是设计模式中最简单的一个类,并且其类图中只有一个类。它的主要原则是该类负责创建自己的对象,并且只有单个对象被创建。
对于那些只需要一个的对象,比如:线程池 (threadpool) 、缓存 (cache) 、注册表 (registry)的对象。
public class Singleton {
private static Singleton instance;
//构造函数为private, 防止该类的实例被错误的创建
private Singleton() {
}
public static Singleton getInstance() {
//保证实例只会被创建一次
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
当多个线程执行时会出现不符合预期的结果
// 实现Runnable接口
public class T implements Runnable{
@Override
public void run(){
Singleton instance = Singleton.getInstance();
System.out.println(Thread.currentThread().getName()+" "+instance);
}
}
// 测试类
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new T());
Thread t2 = new Thread(new T());
t1.start();
t2.start();
}
}
运行结果:
结果分析:
public static Singleton getInstance() {
//保证实例只会被创建一次
[0]if (instance == null) {
[1]instance = new Singleton();
}
[2]return instance;
}
假设两个线程分别叫t0、t1,若通过调试工具手动干预使其执行顺序为:
t0[0]->t1[0]->t0[1]->t0[2]->t1[1]->t1[2]
则会导致出现了两个实例,不符合单例模式的要求
public class Singleton {
private static Singleton instance;
private Singleton() {
}
// 使用synchronized进行同步,对Singleton类加锁
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
虽然通过同步的方法解决了多线程的问题,但是同步影响了执行的性能。并且实例只需要创建一次,但是通过该方法每次调用getInstance() 函数都需要同步,很累赘。
public class Singleton {
// 利用静态变量,实现单例
private static final Singleton instance = new Singleton();
//* 虽然不调用构造函数,需要防止系统自动创建public的构造函数
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
该方案很好的解决多线程带来的问题,并且未使用同步方法。当类加载的时候便创建了其实例,没有延迟加载,可能导致内存的浪费。
public class Singleton {
// 实现细节1:volatile关键字
private static volatile Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
// 实现细节2:两次判断是否为null
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
instance = new Singleton()
因为上面这句代码并非原子操作,实际经历了三个步骤
若不使用volatile关键词,JVM重排序可能导致其程序执行顺序为1->3->2
假设有线程t0、t1,可能出现如下情况:
使用volatile关键字修饰的共享变量使用缓存一致性协议,保证了内存的可见性
若不使用两次检查,假设有线程t0、t1,可能出现如下情况:
此时t0和t1创建了两个不同的实例,违反了单例的原则
代码复杂,在JDK1.4以及更早版本的Java中,volatile关键字的实现会导致双重检查加锁的失效
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
通过该方法可以很好的多线程的问题,并且由于不需要锁,在高并发环境下具有优越的性能。并且其不同于类的静态变量的实现方法,仅在第一次调用getInstance()函数的时候才会创建实例,因此不会出现内存的浪费的情况。
上述实现看似很完美,但是仍有一些问题
原文:https://www.cnblogs.com/YuanJieHe/p/12616921.html