三十天重学设计模式之单例模式

单例模式

单例模式,可能是设计模式中最简单的设计模式之一,同时也可能是最常用的设计模式之一。

单例模式保证了一个类只有一个实例,其思想是将构造器私有化。当我们想要控制实例的个数的时候,就可以考虑使用单例模式。

在日常生活中,我们工作时一个人有一个直属上级领导,数据库的连接池不会反复创建,这些都是单例的例子。

单例模式的实现⽅式⽐较多,主要在实现上是否⽀持懒汉模式、是否线程安全中运⽤各项技巧。当然也有⼀些场景不需要考虑懒加载也就是懒汉模式的情况,会直接使⽤ static 静态类或属性和⽅法的⽅式进⾏处理,供外部调⽤。

七种单例模式的实现

1.懒汉模式(线程不安全)

实现起来很简单,当没有实例时创建实例就好了。在第一次使用才会创建实例,因为没有加锁,所以是线程不安全的,因此不支持多线程模式。

public class LazyMan(){
    
    
     private LazyMan(){
    
    
        //私有化构造方法
    }
    
    private static LazyMan lazyMan;
    
    public static LazyMan getInstance(){
    
    
        if(lazyMan == null){
    
    
            lazy = new LazyMan();
        }
        return lazyMan;
    } 
}

2.懒汉模式(线程安全)

通过加锁后来实现线程安全,但是会减低效率

public class LazyMan(){
    
    
     private LazyMan(){
    
    
        //私有化构造方法
    }
    
    private static LazyMan lazyMan;
    
    public static synchronized LazyMan getInstance(){
    
    
        if(lazyMan == null){
    
    
            lazy = new LazyMan();
        }
        return lazyMan;
    } 
  }
}

3.饿汉模式

顾名思义,比较饥渴,这饥渴程度和我有一拼。因为没有加锁,执行的效率会很高,但是可能会造成内存浪费。

public class Hungry(){
    
    
    private Hungry(){
    
    
        
    }
    
    private final static Hungry hungry = new Hungry();
    public static Hungry getInstance(){
    
    
        return hungry;
    } 
}

4.内部类(线程安全)

扫描二维码关注公众号,回复: 12974680 查看本文章

使⽤类的静态内部类实现的单例模式,既保证了线程安全同时又保证了懒加载,同时不会因为加锁的⽅式耗费性能。这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是⼀个类的构造⽅法在多线程环境下可以被正确的加载。

public class Holder(){
    
    
    private Holder(){
    
    
        
    }
    
    public static class InnerClass(){
    
    
        private final static Holder holder = new Holder();
    }
    
    public static Holder getInstance(){
    
    
        return InnerClass.holder;
    } 
}

5.双重检测锁模式(DCL)

双重锁的⽅式是⽅法级锁的优化,减少了部分获取实例的耗时,同时也满足了懒加载模式。

public class LazyMan(){
    
    
     private LazyMan(){
    
    
        
    }
    private volatile static LazyMan lazyMan;//必须加上volatile保证
    
    public static LazyMan getInstance(){
    
    
        if(lazyMan == null){
    
    
            synchronized(LazyMan.class){
    
    
                if(lazyMan==null){
    
    
                    lazyMan = new LazyMan();//不是原子性操作
                }
            }
        }
        return lazyMan;
   }

6. CAS「AtomicReference」(线程安全)

通过Java并发库提供的原子类AtomicReference,AtomicReference可以封装一个泛型实例,支持并发访问。

使⽤CAS的好处就是不需要使⽤传统的加锁⽅式保证线程安全,⽽是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以⽀持较⼤的并发性。但是它会产生忙等,如果没有将会一直死循环。

public class Singleton(){
    
    
    private Singleton(){
    
    
        
    }
    
    private static Singleton singleton;
    
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference();
    
    public static final Singleton getInstance() {
    
    
     for (; ; ) {
    
     
     	Singleton singleton = INSTANCE.get(); 
     	if (singleton != null) return singleton; 
     	INSTANCE.compareAndSet(null, new Singleton()); 
     	return INSTANCE.get(); 
         } 
     }
}

7.枚举

这种写法在功能上与共有域⽅法相近,但是它更简洁,⽆偿地提供了串⾏化机制,绝对防⽌对此实例化,即使是在⾯对复杂的串⾏化或者反射攻击的时候。虽然这种⽅法还没有⼴泛采⽤,但是单元素的枚举类型已经成为实现Singleton的最佳⽅法,被Effective Java作者推荐。需要注意的是在继承的情况下是不可用的。

public enum Singleton(){
    
    
	INSTANCE;
	
	public void method(){
    
    
	
	}
}

@Test
public void test() {
    
    
 Singleton.INSTANCE.method();
 }

总结:虽然单例模式比较简单,但是在开发中很常见。在平时的开发中如果可以确保此类是全局可⽤不需要做懒加载,那么直接创建并给外部调⽤即可。但如果是很多的类,有些需要在⽤户触发⼀定的条件后(游戏关卡)才显示,那么⼀定要⽤懒加载。

猜你喜欢

转载自blog.csdn.net/MAKEJAVAMAN/article/details/115254924