Loading

单例模式

介绍

在编程开发中经常会遇到这样一种场景,那就是需要保证一个类只有一个实例哪怕多线程同时访问,并需要提供一个全局访问此实例的点。单例模式主要解决的是,一个全局使用的类频繁的创建和消费,从而提升提升整体的代码的性能。

例如:日志系统的对象被反复创建打印

public class Logger {
    private FileWriter writer;

    public Logger() {
        File file = new File("../log.txt");
        writer = new FileWriter(file, true); //true表示追加写入
    }

    public void log(String message) {
        writer.write(mesasge);
    }
}
// Logger类的应用示例:
public class UserController {
    private Logger logger = new Logger();

    public void login(String username, String password) {
        logger.log(username + " logined!");
    }
}

public class OrderController {
    private Logger logger = new Logger();

    public void create(OrderVo order) {
        logger.log("Created an order: " + order.toString());
    }
}

每次打印日志都会生成log对象,造成了资源浪费,日常开发中大致上会出现如上这些场景中使用到单例模式,虽然单例模式并不复杂但是使用面却比较广。

单例模式的实现

要实现一个单例,需要关注的点无外乎下面几个:

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

静态类使用

public class Singleton_00 {
    public static Map<String,String> cache = new ConcurrentHashMap<String, String>(); 
}

在不需要维持任何状态下,仅仅用于全局访问,这个使用使用静态类的方式更加方便

懒汉式(线程不安全)

第一次被引用才会将自己实例化

public class Singleton {
    private static Singleton singleton =null;//prvite堵死外界利用new创造实例的可能
    public Singleton(){
    }
    public static Singleton getInternece(){//全局中唯一能访问到实例的方法
        if(singleton ==null){//实例不存在就new一个
             singleton = new Singleton();
        }
        return singleton;
    }
}

如果有多个访问者访问,就会造成多个同样的实例并存,从而没有达到单例的要求

懒汉式加锁(线程安全)

public class Singleton {
    private static Singleton singleton =null;//prvite堵死外界利用new创造实例的可能
    public Singleton(){
    }
    public static synchronized  Singleton getInternece(){//全局中唯一能访问到实例的方法
        if(singleton ==null){//实例不存在就new一个
             singleton = new Singleton();
        }
        return singleton;
    }
}

如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈,这种实现方式就不可取了

饿汉式单例(线程安全)

不管是否调用 先申请

public  class Singleton {
    private static  Singleton singleton =new Singleton();
    public Singleton(){
    }
    public static Singleton getInternece(){
        return singleton;
    }
}

在类加载的时候,singleton静态实例就已经创建并初始化好了,所以singleton实例的创建过程是线程安全的

静态内部类(线程安全)

它有点类似饿汉式,但又能做到了延迟加载

public class Singleton_04 {

    public  static  class SingletonHolder{
       private static Singleton_04 singleton04=new Singleton_04();
    }

    public  Singleton_04() {
    }

    public  static  Singleton_04 getInstance(){
        return SingletonHolder.singleton04;
    }
}

既保证了线程安全有保证了懒加载,同时不会因为加锁的方式耗费性能。非常推荐使用的一种单例模式

双重锁定机制(线程安全)

判断实例是否为空再上锁,volatile 关键字保障能保证线程有序性,禁止JVM以及处理器进行排序,线程安全效率又高

public class Singleton {
    private static Singleton singleton =null;//prvite堵死外界利用new创造实例的可能
    private static volatile Object object=new Object();
    public Singleton(){
    }
    public static Singleton getInternece(){
            if(singleton ==null){//判断是否为空再上锁
                synchronized (object)
                {
                    singleton = new Singleton();
                }
            }
        return singleton;
    }
}

但是这样如果有两个线程同时调用getInternece方法,都能通过singleton ==null,并且因为synchronized ,一个线程会先创建实例,第二个线程继续等待完毕之后又进入方法创建实例,就达不到单例的目的。

public class Singleton {
    private static Singleton singleton = null;
    private static volatile Object object = new Object();

    public Singleton() {
    }
    public static Singleton getInternece() {
        if (singleton == null) {
            synchronized (object) {
                if (singleton == null)
                    singleton = new Singleton();
            }
        }
        return singleton;
    }
}

枚举(线程安全 推荐)

上面那些是不考虑反射机制和序列化机制的情况下实现的单例模式,但是如果考虑了反射,则上面的单例就无法做到单例类只能有一个实例这种说法了

  @Test
    public  void  Singleton() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Singleton_04 s1=Singleton_04.getInstance();
        Singleton_04 s2=Singleton_04.getInstance();
        System.out.println("s1:"+s1+"\n"+"s2:"+s2);
        System.out.println("正常模式单例"+(s1==s2));

        Constructor<Singleton_04> constructor=Singleton_04.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Singleton_04 s3=constructor.newInstance();
        System.out.println("s3:"+s3+"\n"+"s1:"+s1);
        System.out.println("反射破坏单例"+(s2==s3));
    }

image-20211009125300751

实现枚举单例

public enum  Singleton_06 {
    INSTANCE;
    public Singleton_06 getInstance(){
        return INSTANCE;
    }
}

反射破坏枚举

    @Test
    void  testEnum() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        Singleton_06 singleton06=Singleton_06.INSTANCE;
        Constructor<Singleton_06> constructor2=Singleton_06.class.getDeclaredConstructor();
        constructor2.setAccessible(true);
        Singleton_06 s4=constructor2.newInstance();

        System.out.println("s4:"+s4+"\n"+"singleton06:"+singleton06);
        System.out.println("反射破坏枚举单例"+(s4==singleton06));
    }

破坏失败

image-20211009125923849

posted @ 2021-10-09 15:59  炒焖煎糖板栗  阅读(102)  评论(0编辑  收藏  举报