设计模式--单例模式

单例模式

基本介绍

单例模式(Singleton Design Pattern)

一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫做单例设计模式,简称单例模式。

为什么要使用单例模式?

实战案例一:处理访问资源冲突

public class Logger{
    //线程安全的
    private FilteWriter writer;
    
    public Logger(){
        File file=new File("/xxx");
        writer=new FileWriter(file,true);
    }
    
    public void log(String message){
        writer.write(message);
    }
}

//Logger类的应用示例
public class UserController{
    private Logger logger=new Logger();
    
    public login(String username,String password,String verifyCode){
        logger.log(username+"logined!");
        //***省略逻辑代码
    }
}

public class OrderController{
    private Logger logger=new Logger();
    
    public void create(OrderVO order){
        logger.log("Created an order:"+order.toString());
    }
}

分析:在UserController和OrderController类中分别创建了一个Logger的对象,写入的文件是同一个文件,所有在写入的时候会出现线程竞争的问题。

public class Logger{
    private FileWriter writer;
    
    public Logger(){
        File file=new File("/xxx");
        writer=new FileWriter(file,true);
    }
    
    public void log(String message){
        //类级别的锁
        synchronized(Logger.class){
            writer.write(message);
        }
    }
}

实战案例二:表示全局唯一类

public class IdGenerator{
    /**
    * AtomicLong 是一个Java并发库中提供的一个原子变量类型,
    * 它将一些线程不安全需要加锁的复合操作封装为了线程安全的原子操作
    */
    private AtomicLong id=new AtomicLong(0);
    private static final IdGenerator instance=new IdGenerator();
    private IdGenerator(){}
    
    public static IdGenerator getInstance(){
        return instance;
    }
    
    public long getId(){
        return id.incrementAndGet();
    }
}

//使用举例
long id=IdGenerator.getInstance().getId();

## 如何实现一个单例?

单例的实现方式

关注点:

  • 构造函数需要是private访问权限的,这样才能避免外部通过new的方式创建对象实例
  • 考虑对象创建时的线程安全问题
  • 考虑是否支持延迟加载
  • 考虑getInstance()性能是否高效(是否加锁)
  1. 饿汉式

    public class IdGenerator{
        private AtomicLong id=new AtomicLong(0);
        //创建实例
        private static final IdGenerator instance=new IdGenerator();
        //构造函数设置为私有
        private IdGenerator(){}
        //提供获取实例对象方法
        public static IdGenerator getInstance(){
            return instance;
        }
        //通过线程安全的原子类对象获取id
        public long getId(){
            return id.incrementAndGet();
        }
    }
    

    分析

    • 饿汉式在类加载的时候就创建好实例,把创建对象的工作放到启动的时候,启动时间会受影响,单在程序运行中,会提高速度。

    • 根据fail-fast的设计原则(有问题及时暴露),这个设计也是比较推荐的。

  2. 懒汉式

    public class IdGenerator{
        private AtomicLong id=new AtomicLong(0);
        //创建变量
        private static final IdGenerator instance;
        //构造函数设置为私有
        private IdGenerator(){}
        //提供同步获取实例对象方法
        public static synchronized IdGenerator getInstance(){
            if(instance==null){
            	instance=new IdGenerator();
            }
            return instance;
        }
        //通过线程安全的原子类对象获取id
        public long getId(){
            return id.incrementAndGet();
        }
    }
    

    分析:

    • 懒汉式个getInstance()这个方法加了一把大锁,导致这个函数的并发度很低。
    • 如果频繁的用到,频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈,这种方式推荐使用
  3. 双重检测

    public class IdGenerator{
        private AtomicLong id=new AtomicLong(0);
        //创建变量
        private static volatile final IdGenerator instance;
        //构造函数设置为私有
        private IdGenerator(){}
        //提供同步获取实例对象方法
        public static IdGenerator getInstance(){
            //第一次判断
            if(instance==null){ 
                synchronized(IdGenerator.class){
                    //第二次判断
                    if(instance==null){
            			instance=new IdGenerator();
                    }
                }
            }
            return instance;
        }
        //通过线程安全的原子类对象获取id
        public long getId(){
            return id.incrementAndGet();
        }
    }
    

    分析:双重检测模式看似没有问题,但在低版本的jdk中会出现指令重排问题,为避免出现线程问题,还是加上volatile字段比较好。

  4. 静态内部类

    public class IdGenerator{
        private AtomicLong id=new AtomicLong(0);
        
        //私有化构造函数
        private IdGenerator(){}
        
        //静态内部类
        private static class SingletonHolder{
            private static final IdGenerator instance=new IdGenerator();
        }
        //获取对象实例
        public static IdGenerator getInstance(){
            return SingletonHolder.instance;
        }
        
        public long getId(){
            return id.incrementAndGet();
        }
    }
    

    分析

    • 当外部类IdGenerator加载的时候,并不会创建SingletonHolder实例对象。只有当调用getInstance()方法时,SingletonHolder才会被加载,这个时候才会创建instance.
    • instance的唯一性、创建过程的线程安全性,都由JVM来保证。
    • 这种实现方式既保证了线程安全,又能做到延迟加载。
  5. 枚举

    public enum IdGenerator{
        INSTANCE;
        private AtomicLong id=new AtomicLong(0);
        
        public long getId(){
            return id.incrementAndGet();
        }
    }
    

    单例模式的优略

    单例存在哪些问题?

    • 单例对OOP特性的支持不友好
    • 单例会隐藏类之间的依赖关系
    • 单例对代码的扩展性不友好
    • 单例对代码的可测试性不友好
    • 单例不支持有参数的构造函数

    单例有什么解决方案?

    • 可以用其他设计模式来代替。例如:工厂模式,IOC容器
    • 单例类要根据具体的场景来适配

    单例模式的唯一性理解

    • 进程唯一:单例类中对象的唯一性的作用范围是进程唯一的,进程唯一指的是进程内唯一,进程间唯一
    • 线程唯一:线程唯一指的是线程内唯一,线程间可以不唯一。
    • 集群唯一:指的是进程内唯一,进程间也唯一。

    实现线程内的单例

    public class IdGenerator{
        private AtomicLong id=new AtomicLong(0);
        
        private static final ConcurrentHashMap<Long,IdGenerator> instances=new ConcurrentHashMap<>();
        private IdGenerator(){}
        
        public static IdGenerator getInstance(){
            Long currentThreadId=Thread.currentThread().getId();
            instances.putIfAbsent(currentThreadId,new IdGenerator());
            return instances.get(currentThreadId);
        }
        
        public long getId(){
            return id.incrementAndGet();
        }
    }
    

    分析:java语言提供了ThreadLocal类可以简单实现线程唯一,也可以自己实现。

    实现集群环境下的单例

    public class IdGenerator{
        private AtomicLong id=new AtomicLong(0);
        
        private static IdGenerator instance;
        //集群级别的共享存储,如 redis
        private static SharedObjectStorage storage=FileSharedObjectStorage();
        //分布式锁
        private static DistributedLock lock=new DistributeLock();
        
        public synchronized static IdGenerator getInstance(){
            if(instance==null){
                instance=storage.load(IdGenerator.class);
            }
            return instance;
        }
        
        public synchronized void freeInstance(){
            sotrage.save(this,IdGenerator.class);
            instance=null;
            lock.unlock();
        }
        
        public long getId(){
            return id.incrementAndGet();
        }
    }
    
    
    //IdGenerator应用举例
    IdGenerator instance=IdGenerator.getInstance();
    long id=idGenerator.getId();
    IdGenerator.freeInstance();
    

    分析:为保证任何时刻,在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,还需要显示地对内存中删除,并且释放对对象的加锁。

你可能感兴趣的:(设计模式,设计模式)