介绍
在编程开发中经常会遇到这样一种场景,那就是需要保证一个类只有一个实例哪怕多线程同时访问,并需要提供一个全局访问此实例的点。单例模式主要解决的是,一个全局使用的类频繁的创建和消费,从而提升提升整体的代码的性能。
例如:日志系统的对象被反复创建打印
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)); }
实现枚举单例
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)); }
破坏失败