单例模式,就是一个类只能实例化一个对象;
在多线程下,一个常见的模板是这样的
package singleton;
public class PrintSpoolerSingleton
{
private volatile static PrintSpoolerSingleton instance = null;
private PrintSpoolerSingleton()
{
}
public static PrintSpoolerSingleton getInstance() throws
{
if(instance == null){
System.out.println("创建打印池!");
synchronized (PrintSpoolerSingleton.class){
if(instance == null){
instance = new PrintSpoolerSingleton();
}
}
}
return instance;
}
}
构造方法私有---这样保证外部无法通过new创造对象,保证单一;
实例化对象private且static---提供一个静态getInstance来获取单例对象,而静态方法里面需要静态对象;
饿汉式和懒汉式---上面的代码是懒汉式,饿汉式则是在初始化时就实例化好
private static PrintSpoolerSingleton instance = new PrintSpoolerSingleton();
你们可能想这样写
public synchronized static PrintSpoolerSingleton getInstance()
{
if(instance == null){
instance = new PrintSpoolerSingleton();
}
return instance;
}
但是这样写在并发下效率比较低;
因为每次请求都需要锁,但是我们其实只是“读”而已;
“读”的过程并不需要同步,只有在new的时候才需要,所以应该是在判断为null是再加锁;
改成下面这种
public static PrintSpoolerSingleton getInstance() throws PrintSpoolerException
{
if(instance == null){
synchronized(PrintSpoolerSingleton.class){
instance = new PrintSpoolerSingleton();
}
}
return instance;
}
然而,这种也有问题;
如果有两个线程同时判断到instance为null,那它们都会进到里面,最后出现多次实例化;
那么就写成这样
public static PrintSpoolerSingleton getInstance() throws
{
if(instance == null){
synchronized (PrintSpoolerSingleton.class){
if(instance == null){
instance = new PrintSpoolerSingleton();
}
}
}
return instance;
}
用了双重锁之后,其实还不够完善,这里涉及到JVM指定优化方面的问题;
问题出在下面这句
instance = new PrintSpoolerSingleton();
虚拟机做了三个步骤:
1.给instance分配内存
2.调用构造方法完成初始化
3.使instance对象的引用指向分配的内存空间
完成第3步时instance就不为null了,而JVM优化时使得指令可能不会按照1-2-3的顺序执行,这样就可能造成还没有真正的实例化成功,一个线程判断发现不为null,然后就返回instance了;
所以你会看到对象变量上面还有一个关键词volatile,它能防止指令重排
private volatile static PrintSpoolerSingleton instance = null;
原文:https://www.cnblogs.com/Jayyi/p/12852832.html