Java单例模式详解
一、基本概念
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。怎样才能保证一个类只有一个实例并且这个实例易于被访问呢?一个全局变量使得一个对象可以被访问,但它不能防止你实例化多个对象,一个更好的方法是让类自身负责保存他的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法,这就是Singleton模式。
- 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
- 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
二、经典实现
1、上述代码为单例模式的懒汉式,在第一次调用的时候实例化。
package com.yunhe.singleton; //懒汉式单例类,在第一次调用的时候实例化 public class Singleton { //注意这里没有final private static Singleton instance=null; //私有的构造方法 private Singleton(){ } //静态工厂方法 public static Singleton getInstance(){ if(instance==null){ return new Singleton(); } return instance; } }
Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个JVM虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。(事实上,通过Java反射机制是能够实例化这种类(即构造方法为private类型)的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)
但是以上实现没有考虑线程安全问题。所谓线程安全是指:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。显然以上实现并不满足线程安全的要求,在并发环境下很可能出现多个Singleton实例。
所以,如果在多线程下面操纵这一单例,需要自getInstance的静态方法上面添加sychronized。
2、下面是单例模式的饿汉式,在类初始化时,已经自行实例化。
package com.yunhe.singleton;
//饿汉式单例,在类初始化时,已经自行实例化 public class Singleton2 { //已经自行实例化 private static final Singleton2 instance=new Singleton2(); //私有的默认构造函数 private Singleton2(){ } //静态工厂方法 public static Singleton2 getInstance(){ return instance; } }
3、下面是单例模式的登记式
注:static就是在类被第一次加载的时候执行,以后就不再执行。
package com.yunhe.singleton; import java.util.HashMap; import java.util.Map; //登记式单例类 //类似Spring里面的方法,将类名注册,下次从里面直接获取。 public class Singleton3 { private static Map<String,Singleton3> map = new HashMap<String,Singleton3>(); static{ Singleton3 instance = new Singleton3(); map.put(instance.getClass().getName(), instance); } //保护的默认构造函数 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 instance3 = Singleton3.getInstance(null); System.out.println(instance3.about()); } }
三、单例模式相关示例
示例一:
package com.yunhe.singleton; public class TestPerson { //注意之类没有final private static TestPerson tp1=null; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } //私有无参构造方法 private TestPerson(){} //这个类必须自动向整个系统提供这个实例对象 public static TestPerson getIntance(){ if(tp1==null){ tp1=new TestPerson(); } return tp1; } public void getInfo(){ System.out.println("output message: "+name); } }
客户端调用代码:
package com.yunhe.singleton; public class TestMain { public static void main(String [] args){ TestPerson s=TestPerson.getIntance(); s.setName("刘德华"); System.out.println(s.getName()); TestPerson s1=TestPerson.getIntance(); s1.setName("张信哲"); System.out.println(s1.getName()); s.getInfo(); s1.getInfo(); if(s==s1){ System.out.println("创建的是同一个实例"); }else if(s!=s1){ System.out.println("创建的不是同一个实例"); }else{ System.out.println("application error"); } } }
运行结果:
刘德华
张信哲
output message: 张信哲
output message: 张信哲
创建的是同一个实例
示例二:
这一个产生随机数的例子,整个应用程序中只需要一个类的实例来产生随机数,客户端程序从类中获取这个实例,调用这个实例的方法nextInt(),公用的方法访问需要进行同步,这是单例模式需要解决的同步问题。
单例模式中需要解决的重要问题是方法的同步问题,同步的粒度有多大等。在本例子中同在获得类的实例的时候使用了同步,代码如下:
package com.yunhe.singleton; import java.util.Random; public class Singleton { private Random generator; private static Singleton instance; private Singleton() { generator = new Random(); } public void setSeed(int seed) { generator.setSeed(seed); } public int nextInt() { return generator.nextInt(); } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
客户端调用代码:
package com.yunhe.singleton; public class Client{ public static void main(String[] args){ Singleton s1 = Singleton.getInstance(); System.out.println(s1.toString()); for(int i=0;i<10;i++){ Singleton s2 = Singleton.getInstance(); System.out.println("The randomed number is "+s2.toString()); } } }
运行结果:
com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
The randomed number is com.yunhe.Singleton@61de33
总结:单例模式除了上述提到的常见应用之外,在实际编程中应用的也非常广泛,可以根据需要应用该模式,学以致用才是最重要的。使得应用程序在运行时保持只能有一个实例,在一些大的应用程序中,主程序只需要有一个,因此需要使用单例模式