1.java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。
单例模式有一下特点:
1、单例类只能有一个实例。
2、单例类必须自己自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
正是由于这个特点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或 文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境 下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。
2.Java单例模式3种写法:
(1)懒汉:
1 public class Singleton { 2 private static Singleton instance; 3 private Singleton (){} 4 public static Singleton getInstance() { 5 if (instance == null) { 6 instance = new Singleton(); 7 } 8 return instance; 9 } 10 } 11
致命的是在多线程不能正常工作,线程不安全。
以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,对getInstance这个方法改造有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全:
优化懒汉,实现线程安全。做法如下3种:
•在getInstance方法上加同步:
public static synchronized Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; }
•双重检查锁定:
public static synchronized Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; }
•静态内部类:
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return LazyHolder.INSTANCE; } }
(2)饿汉:
//饿汉式单例类.在类初始化时,已经自行实例化 public class Singleton1 { private Singleton1() {} private static final Singleton1 single = new Singleton1(); //静态工厂方法 public static Singleton1 getInstance() { return single; } }
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
其实饿汉还有变种编写方式如下:
1 public class Singleton1 { 2 private Singleton1 instance = null; 3 static { 4 instance = new Singleton1(); 5 } 6 private Singleton1 (){} 7 public static Singleton1 getInstance() { 8 return this.instance; 9 } 10 } 11
表面上看起来差别挺大,其实更第三种方式差不多,都是在类初始化即实例化instance。
(3)登记式单例(可忽略)
//类似Spring里面的方法,将类名注册,下次从里面直接获取。 public class Singleton3 { private static Map<String,Singleton3> map = new HashMap<String,Singleton3>(); static{ Singleton3 single = new Singleton3(); map.put(single.getClass().getName(), single); } //保护的默认构造子 protected Singleton3(){} //静态工厂方法,返还此类惟一的实例 public static Singleton3 getInstance(String name) { if(name == null) { name = Singleton3.class.getName(); System.out.println("name == null"+"--->name="+name); } if(map.get(name) == null) { try { map.put(name, (Singleton3) Class.forName(name).newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return map.get(name); } //一个示意性的商业方法 public String about() { return "Hello, I am RegSingleton."; } public static void main(String[] args) { Singleton3 single3 = Singleton3.getInstance(null); System.out.println(single3.about()); } }
登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。 这里我对登记式单例标记了可忽略,我的理解来说,首先它用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。
(4)枚举实现单例模式:
单例模式约束一个类只能实例化一个对象。在Java中,为了强制只实例化一个对象,最好的方法是使用一个枚举量。这个优秀的思想直接源于Joshua Bloch的《Effective Java》(《Java高效编程指南》)。如果你的藏书室里还没有这本书,请搞一本,它是迄今为止最优秀的Java书籍之一。
这里有几个原因关于为什么在Java中宁愿使用一个枚举量来实现单例模式:
♦ 自由序列化;
♦ 保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);
♦ 线程安全;
案例:
1 public enum AnimalHelperSingleton { 2 3 INSTANCE; 4 5 private AnimalHelperSingleton(){ 6 7 } 8 9 public Animal[] buildAnimalList(){ 10 final Animal[] animals = new Animal[10]; 11 12 animals[0] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 13 "Dog", true, Color.GRAY); 14 animals[1] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 15 "Cat", true, Color.YELLOW); 16 animals[2] = new SimpleAnimal(Animal.AnimalClass.AMPHIBIAN, 17 "Frog", true, Color.GREEN); 18 animals[3] = new SimpleAnimal(Animal.AnimalClass.BIRD, 19 "Crow", true, Color.BLACK); 20 animals[4] = new SimpleAnimal(Animal.AnimalClass.BIRD, 21 "Cardinal", true, Color.RED); 22 animals[5] = new SimpleAnimal(Animal.AnimalClass.ARTHROPOD, 23 "Mantis", false, Color.GREEN); 24 animals[6] = new SimpleAnimal(Animal.AnimalClass.ARTHROPOD, 25 "Spider", false, Color.ORANGE); 26 animals[7] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 27 "Tiger", true, Color.ORANGE); 28 animals[8] = new SimpleAnimal(Animal.AnimalClass.MAMMAL, 29 "Bear", true, Color.BLACK); 30 animals[9] = new SimpleAnimal(Animal.AnimalClass.BIRD, 31 "Owl", true, Color.BLACK); 32 33 return animals; 34 } 35 36 }
如何使用:
//Call singleton to build the animal list. Animal[] animals = AnimalHelperSingleton.INSTANCE.buildAnimalList();
这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
3.小结:
(1)饿汉式和懒汉式区别:
从名字上来说,饿汉和懒汉,
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
->1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。
->2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其 资源已经初始化完成,而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能 上会有些延迟,之后就和饿汉式一样了。
(2)什么是线程安全:
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。