设计模式——单例模式
单例模式要求类能够返回对象的一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用 getInstance 这个名称)
单例模式定义:
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设置成单例模式。这些应用都或多或少具有资源管理器的功能。
单例模式的特点:
1)单例类只能有一个实例
2)单例类必须自己创建自己的唯一实例
3)单例类必须给所有其他对象提供者这个实例
单例模式的实现主要通过以下两个步骤:
1)将该类的构造方法定义为私有方法,这样其它处的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供提供的静态方法来得到该类的唯一实例
2)在该类内提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类持有的引用为空就创建该类的实例对象并将实例对象的引用赋予该类保持的引用
注意事项
单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例对象的存在,从而同时各自创建了一个实例对象,这样就有两个实例被构造出来,于是违反了单例模式中实例唯一的原则。解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(不过这样会降低效率)。
实现单例模式的方式 (饿汉式2种都线程安全 懒汉式5种3个线程安全 枚举式1种)
饿汉式单例(立即加载方式)
1 package com.test.singleton; 2 3 public class Singleton { 4 // 饿汉式 静态常量 线程安全 可用 5 private final static Singleton INSTANCE = new Singleton(); 6 private Singleton(){ 7 System.out.println("I am the Singleton"); 8 } 9 public static Singleton getInstance(){ 10 return INSTANCE; 11 } 12 }
// 饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。
// 优点:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题
// 缺点:在类装载的时候就完成实例化,没法达到 lazy loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
静态代码块方式(另一种饿汉式单例,立即加载方式)
1 package com.test.singleton; 2 3 public class Singleton { 4 // 饿汉式 静态代码块 线程安全 可用 5 private static Singleton instance; 6 static { 7 instance = new Singleton(); 8 } 9 private Singleton(){ 10 System.out.println("I am the Singleton"); 11 } 12 public Singleton getInstance(){ 13 return instance; 14 } 15 }
// 该方式与上面的饿汉式方式类似
// 优点:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题
// 缺点:在类装载的时候就完成实例化,没法达到 lazy loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
懒汉式 (延迟加载方式)
1 package com.test.singleton; 2 3 public class Singleton { 4 // 懒汉式 线程不安全 不可用 5 private static Singleton instance; 6 private Singleton(){ 7 System.out.println("I am the Singleton"); 8 } 9 public static Singleton getInstance(){ 10 if (instance == null){ 11 instance = new Singleton(); 12 } 13 return instance; 14 } 15 }
// 优点:这种写法起到了 lazy loading 的效果,但是只能在单线程下使用
// 缺点:在多线程环境下不能使用这种方式。如果在多线程下,一个线程进入了 if(instance == null)判断语句,还未来得及往下执行,另一个线程也通过了这个判断语句,这是便会产生多个实例。
懒汉式(同步方法)
1 package com.test.singleton; 2 3 public class Singleton { 4 // 懒汉式 线程安全 同步方法 效率低 不推荐使用 5 private static Singleton instance; 6 private Singleton(){ 7 System.out.println("I am the Single"); 8 } 9 private static synchronized Singleton getInstance(){ 10 if (instance == null){ 11 instance = new Singleton(); 12 } 13 return instance; 14 } 15 }
// 优点:对getInstance()方法做了线程同步,解决了多线程不安全的问题
// 缺点:效率太低,每个线程在想获得类的实例的时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类的实例,直接return就行了。方法进行同步效率太低要改进。
懒汉式(同步代码块)不可用
1 package com.test.singleton; 2 3 public class Singleton { 4 // 懒汉式 线程不安全 同步代码块 不可用 5 private static Singleton instance; 6 private Singleton(){ 7 System.out.println("I am the Singleton"); 8 } 9 public static Singleton getInstance() { 10 if (instance == null) { 11 synchronized (Singleton.class) { 12 instance = new Singleton(); 13 } 14 } 15 return instance; 16 } 17 }
// 由于同步方法效率太低,改用同步代码块,但是这种同步并不能起到线程同步的作用
// 假如一个线程进入了if(instance == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这是便会产生多个实例
懒汉式(同步代码块+双重检验锁)
1 package com.test.singleton; 2 3 public class Singleton { 4 // 懒汉式 双重检验 线程安全 推荐使用 5 private volatile static Singleton instance; 6 private Singleton(){ 7 System.out.println("I am the Singleton"); 8 } 9 public static Singleton getInstance(){ 10 if (instance == null){ 11 synchronized (Singleton.class){ 12 if (instance == null){ 13 instance = new Singleton(); 14 } 15 } 16 } 17 return instance; 18 } 19 }
// 优点:线程安全 延迟加载 效率较高
// 在代码中进行了两次if(instance == null)检查,这样就可以保证线程安全了。实例化代码只用执行一次,后面再次访问时,判断if(instance == null),直接return实例化对象
懒汉式(静态内部类)
1 package com.test.singleton; 2 3 public class Singleton { 4 // 懒汉式 静态内部类 推荐使用 5 private Singleton(){ 6 System.out.println("I am the Singleton"); 7 } 8 private static class SingletonInstance{ 9 private static final Singleton instance = new Singleton(); 10 } 11 public static Singleton getInstance(){ 12 return SingletonInstance.instance; 13 } 14 }
// 优点:线程安全 延迟加载 效率高
// 与饿汉式类似,都采用了类装载机制来保证初始化实例时只有一个线程,不同的地方是饿汉式是只要Singleton类被装载就会实例化,没有 lazy loading 的作用。
// 而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance()方法时,才会装载SingletonInstance类,从而完成了Singleton类的
// 实例化。类的静态属性只会在加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
// 静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。这种情况不多做说明了,使用时请注意。
枚举方式
1 package com.test.singleton; 2 3 public enum Sinlgeton { 4 // 枚举 推荐用 5 INSSTANCE; 6 public void whateverMethod(){ 7 8 } 9 }
// 优点:系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
// 缺点:当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用 new, 这可能会给其他开发人员造成困扰,特别是看不到源码的时候