单例模式
< 单例模式简而言之就是某各类只能被实例化一次 ,JVM中不能存在两个对象 。 实现该模式的方式也比较简单,就是将类的构造器设置成私有的,然后提供一个公开的方法获取该唯一的实例 ,以下是具体的单例类实现方式>
第一种实现方式
public class Singleton {
private static Singleton single;
//私有构造器阻止外部类的随意创建
private Singleton () {
}
//提供一个公开的方法访问实例
public static Singleton getInstance() {
if(single == null){ //1
single = new Singleton(); //2
}
return single;
}
}
这种方式存在一个缺点是线程不安全。想想当两个线程都调用getInstance方法时,都通过了1处代码且没有执行2处代码。当他们执行2处代码时将会创建两个实例,违背了单例类的初中。所以这里我们进行改进一下给方法加上锁。
public class Singleton {
private static Singleton single;
//私有构造器阻止外部类的随意创建
private Singleton () {
}
//提供一个公开的方法访问实例
public synchronized static Singleton getInstance() {
if(single == null){
single = new Singleton();
}
return single;
}
}
确实改进之后线程安全了,也不会存在创建两个实例对象的风险了。但是这又会存在一个新的问题就是效率问题。我们都知道在Java中对方法进行加锁之后系统性能会降低很多。而且这里给方法加锁只是为了避免第一次单例对象为null时出现的错误,一旦单例对象被初始化就不会出现线程不安全的问题了,只使得这个加锁动作是多余的(因为只有在第一次创建的时候会有帮助,之后就基本是累赘了)。于是我们又进行新一轮的改进,将锁加在if语句里面而不是方法上,变成这样。
public class Singleton {
private static Singleton single;
//私有构造器阻止外部类的随意创建
private Singleton () {
}
//提供一个公开的方法访问实例
public static Singleton getInstance() {
if(single == null){ //1
synchronized(Singleton.class){ //2
single = new Singleton();
}
}
return single;
}
}
这样做是否正确呢?这样是存在问题的!当我们两个以上的线程都通过1处的判断时尽管对Singleton加锁了,但是还是会创建两个实例对象,所以我们必须还要进行一次判断,变成这样。
public class Singleton {
private static Singleton single;
//私有构造器阻止外部类的随意创建
private Singleton () {
}
//提供一个公开的方法访问实例
public static Singleton getInstance() {
if(single == null){ //1
synchronized(Singleton.class){
if(single == null){ //2
single = new Singleton(); //3
}
}
}
return single;
}
}
这样问题就解决了,即使多个线程进入1处,但是2处将还会进行一次检查,所以不存在创建多个个实例的风险了,这种方式双重检查。所以到这里你以为真的就没问题了吗?错(太年起)!
由于这里涉及很多底层知识,我们简单讲一下存在的问题。由于创建一个实例对象可分为多个步骤(即3处代码),可能要检查类加载器, 分配内存,调用构造器,赋值给引用变量等,这些步骤顺序执行当然没问题 , 但是Cpu由于要优化性能,可能会对指令进行重排序,即打乱这些指令的步骤,可能引用对象还没有初始化就被赋值了,而此时可能切换到另外一个调用该方法的线程中去,该线程正在执行1处代码,发现single不为空,于是返回一个还没有初始化的实例对象。对于这种情况我们只能通过给Singleton 加上volatile关键字来防止命令的重排序。
public class Singleton {
private static volatile Singleton single;
//私有构造器阻止外部类的随意创建
private Singleton () {
}
//提供一个公开的方法访问实例
public static Singleton getInstance() {
if(single == null){ //1
synchronized(Singleton.class){
if(single == null){ //2
single = new Singleton(); //3
}
}
}
return single;
}
}
到这里所有问题都解决了,实现了一个延时加载线程安全的双层检(double check)查的单例模式。
第二种实现方式
更为简单且不存在线程安全问题,也不需要双层检查。如下代码:
public class Singleton {
private static Singleton single = new Singleton();
//私有构造器阻止外部类的随意创建
private Singleton() {
}
//提供一个公开的方法访问实例
public static Singleton getInstance() {
return single;
}
}
该方法又叫饿汉模式,在一开始就将实例对象创建好了。该方法唯一缺点就是开始未被调用时该实例占用了内存资源。
第三种实现方式
Java给我们实现的,又叫天然的单例类。
public enum EnumSingleton {
/**
* 天然单例类
*/
INSTANCE;
private EnumSingleton() {
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
采用java提供的枚举类型实现天然单例类,和第二中原理差不多。
第四种实现方式
public class StaticInnerSingleton {
/**
* 延时加载 内部类(线程安全)
* @author lenovo
*
*/
public static class StaticInner{
private static StaticInnerSingleton single = new StaticInnerSingleton();
}
private StaticInnerSingleton() {
}
public static StaticInnerSingleton getInstance() {
return StaticInner.single;
}
}
该方式采用了内部类是延时加载的机制来设计的,也就是说该静态内部类只有在第一次被调用是才会加载。