单例模型:要求一个类只有一个实例,并且对外提供全局的访问方法。
1.懒汉式和饿汉式的单例模型
1.1 懒汉式
懒汉式单例模式,是在类被加载的时候,通过静态初始化的方式实例化。
public class Layzerbones {
private static Layzerbones layzer = new Layzerbones();
private Layzerbones(){}
public static Layzerbones getInstance(){
return layzer;
}
}
1.2 饿汉式
饿汉式单例模式,是在第一次被调用的时候进行实例化;
public class Badmash {
private static Badmash badmash;
private Badmash(){}
public static synchronized Badmash getInstance(){//方法一
if(badmash == null){
badmash = new Badmash();
}
return badmash ;
}
public static Badmash getInstance2(){//方法二
if(badmash == null){
synchronized (Badmash.class){
if(badmash == null){
badmash = new Badmash();
}
}
}
return badmash ;
}
}
上面两种单例模型的共同点:
1.当前类做为静态成员变量;2.构造器私有化;3.提供静态可访问实例化方法;
不同点在于:
1.懒汉式:线程安全;
2.饿汉式,线程不安全,所以要增加锁;
3.饿汉式的锁,有两种方式,一种是在方法上加锁,锁是类的字节码文件;另一种是synchorinize代码块,可以自定义锁
4.synchorinize代码块的双重校验的目的是提高执行的效率;
2.饿汉式的双重检查锁定的问题及优化
问题在于现有的逻辑代码,在编译器编译期间可能会发生重排序的情况;
这个是《JAVA并发编程艺术》中的附图,很清晰的说明了问题;如下说所示;
正常对象的初始化顺序是:1.先给对象分配内存空间;2.初始化对象;3.将初始化的实例指向分配的内存地址
但是问题在于,由于JMM内部重排序,初始化对象,和实例地址指向内存地址的可以重排序;
那么出现的问题就会有:在对象还没有初始化,就已经指向了内存地址,对象为null;当另一个线程再次访问的时候,又会进行判断,为null,重新进行初始化对象。
当然,对于存在的这种问题,比较好解决的,给静态成员增加volatile修饰符,这是一个轻量锁,使修饰的变量具有原子性;
private volatile static Badmash badmash;
当然还有其他方法也可以,避免这种问题。
3.静态内部类单例设计模型
为了避免上面的对象初始化重排的问题,可以利用类在初始化过程中加锁的原理,通过内部类实现单例;
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return Inner.instance;
}
private static class Inner {
private static final Singleton instance = new Singleton();
}
}
4.枚举单例模式
1.在枚举中设置实例枚举INSTINCT;
2.然后再私有的构造器中对需要初始化的对象进行初始化;
3.提供对外的非静态的获取实例的方法
public enum SingleEnum {
INSTINCT;
private User user ;
SingleEnum(){
user = new User();
}
public User getInstance(){
return user;
}
}
以上就是最常见的单例模型!
至此,完毕!