java六种单例模式

一、饿汉式
public class Single {
    //饿汉式,类初始化的时候就加载,无法做到延迟加载,也就没办法减少负载
     private static Single single=new Single();
     private Single(){
    }
     public static Single getSingle(){
         return single;
     }


     // 1、private static表示类初始化时就会对静态属性赋初值,所以一开始就创建对象
     // 2、私有化构造器表示不可以在外部类通过new()创建对象
     // 3、public static表示可以通过Single.getSingle()获得唯一对象
 
 
二、懒汉式 
public class Single {
    //懒汉法,虽然实现了延迟加载,但是线程不安全,如果有两个线程同时调用getSingle()方法,就可能导致重复创建对象
    private static Single single = null;
    private Single(){

}
    public static Single getSingle(){
        if(single ==null){
            single=new Single();
        }
        return single;
    }
// 1、静态私有成员为null,没有初始化
// 2、什么时候调用getSingle()方法,什么时候再创建唯一对象
}


三、考虑线程安全
public class Single {
    private static volatile Single single = null;
    private Single(){ }
    public static Single getSingle(){
        synchronized (Single.class){
        if(single ==null){
            single=new Single();
        }
        return single;
}
}
    // 1、对single对象使用volatile关键字进行限制(volatile关键字修饰的属性直接从内存中获取,不从缓存区获取),
    // 保证其对所有线程的可见性,并且禁止对其进行指令重排序优化
    // 2、为了保证线程安全,在对single是否为null和new的部分使用synchronized进行加锁
    // 3、效率低下,每次调用getSingle()方法,都要在synchronized中排队,而真正需要new的时候比较少


四、兼顾效率和线程安全
public class Single {
    private static volatile Single single = null;
    private Single() {
    }
    public static Single getSingle() {
        if (single == null) {
            synchronized (Single.class) {
                if (single == null) {
                    single = new Single();
                }
            }
        }
        return single;
    }
}


    // 1、双重检查锁,就是在getSingle()的时候进行两次null检查,极大提高了并发度和性能
    // 2、就像上文说的,在单例中new的情况非常少,绝大多数都是可以并行的读操作。

    // 因此在加锁前多进行一次null检查就可以减少绝大多数的加锁操作,执行效率提高的目的也就达到了。


volatile关键字的两层语义

第一层,可见性,可见性指的是一个线程对该变量的修改会马上由工作内存写回主内存,所以会马上反应在其他线程上的读取操作中,顺便一提,工作内存和主内存可以近似理解成实际电脑的高速缓存和主存,工作内存是线程独享的,主存是线程共享的,

第二层,禁止指令重排序优化,多线程代码,由于编译器优化,在实际执行的时候可能与我们编写的顺序不同,编译器只保证程序和执行结果与源代码相同,却不保证实际指令的顺序和源代码相同,这种乱序导致严重问题,volatile可以从语义上解决这个问题

JDK1.5之前即使声明成volatile也无法完全避免重排序导致的问题,所以,1.5之前,双重检查锁形式也无法保证线程安全的


五、静态内部类法

我们可以把Single实例放到一个静态内部类,这样就避免了静态实例在Singleton类加载的时候就创建对象,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全

public class Singleton {
     private static class Holder {
         private static Singleton singleton = new Singleton ( ) ;
     }
    
     private Singleton ( ) { }
        
     public static Singleton getSingleton ( ) {
         return Holder . singleton ;
     }
}


六、枚举写法

一种更优雅的方式来实现单例

使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,Effective Java推荐尽可能地使用枚举来实现单例。

3
4
5
6
7
8
9
10
public enum Singleton {
     INSTANCE ;
     private String name ;
     public String getName ( ) {
         return name ;
     }
     public void setName ( String name ) {
         this . name = name ;
     }
}

最后,不管采取何种方案,请时刻牢记单例的三大要点:

  • 线程安全
  • 延迟加载
  • 序列化与反序列化安全


3
4
5
6
7
8
9
10
public enum Singleton {
     INSTANCE ;
     private String name ;
     public String getName ( ) {
         return name ;
     }
     public void setName ( String name ) {
         this . name = name ;
     }
}
Java
1
2
3
4
5
6
7
8
9
10
public enum Singleton {
     INSTANCE ;
     private String name ;
     public String getName ( ) {
         return name ;
     }
     public void setName ( String name ) {
         this . name = name ;
     }

猜你喜欢

转载自blog.csdn.net/weixin_38118016/article/details/79585139