单例类需要满足以下几个点
- 1.单例类只能有一个实例
- 2.实例只能自己生成
- 3.像其他所有对象提供这一实例
单例模式的应用
- 多线程情况下保证资源的一致性,例如多台打印机打印一个文件。
单例模式的写法
饿汉式
线程安全
public class EagerSingleton
{
private static EagerSingleton instance = new EagerSingleton();
private EagerSingleton()
{
}
public static EagerSingleton getInstance()
{
return instance;
}
}
饿汉式就是在调用getInstance这个方法的时候实例直接被创建,并不进行判断实例是否为空。由于实例是被直接创建,所以并不可以延迟加载(Lazy Loading),容易产生垃圾对象。
还有一个问题,饿汉式是如何保证线程安全的?
假如线程1实例化一个EagerSingleton的同时线程2也在实例化,那么对象不就产生两个内存地址了嘛?事实上是java虚拟机帮我们解决了这个问题。
JVM的解决办法
当jvm遇到new这个关键字的时候,首先会检查符号引用是否已经存在常量池中,(这句话的意思就是新创建的对象是否已经存在),如果没有的话就开始为新建的对象分配内存。
分配内存有两种方式:假如内存规整,就用第一种指针碰撞(Bump the Pointer),就是向空闲的内存区挪动一段位移。假如内存不规整,就采用空闲列表(Free List),从列表上选取一段足够大的内存。
对象创建是很频繁的行为,如果线程1给A分配内存的时候,线程2也在给B分配内存,无论采取何种方式分配内存,都有可能将B的内存分配给A。
解决问题的两种方案:一种是对分配动作同步处理,采用CAS配上失败重试的方式保证更新操作的原子性。另一种将先对线程分配空间,在他们自己的空间里进行操作,称之为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),分配空间用完时,才需要同步锁定。
懒汉式
顾名思义,是可以Lazy Loading的,但是线程不安全
public class LazySingleton
{
private static volatile LazySingleton instance = null;
private LazySingleton()
{
}
public static LazySingleton getInstance()
{
if (instance == null)
instance = new LazySingleton();
return instance;
}
}
双检索
线程安全,Lazy Loading
public class DoubleCheckLockSingleton
{
private static DoubleCheckLockSingleton instance = null;
private DoubleCheckLockSingleton()
{
}
public static DoubleCheckLockSingleton getInstance()
{
if (instance == null)
{
synchronized (DoubleCheckLockSingleton.class)
{
if (instance == null)
instance = new DoubleCheckLockSingleton();
}
}
return instance;
}
}