单例模式确保一个类只会有一个实例,而且会提供一个全局访问点,特别用于资源敏感的对象。在实现的时候要考虑多线程环境,就需要同步。在Singleton4实现中用双重检查加锁,减少同步粒度,从而只会在第一次(不超过两次)请求该实例的时候才会进行同步,在这里,之所以要Double Check的原因是:假设线程P1,P2都执行到了pointA处,此时P1获得对象锁,进入了同步块,然后发现此时uniqueInstance的确为空,所以构造一个实例,退出同步块,释放锁,而后P2获得锁进入同步区域, 在这时如果不再次判断uniqueInstance非空的话,会再次构建一个实例,从而不再单例。
代码如下:
package singleton;
//经典实现方式,但是在多线程环境下就会出问题,
//可能连个线程同时进入了uniqueInstance==null那条控制路径
public class Singleton {
private static Singleton uniqueInstance;
private Singleton() {
} //
public static Singleton getInstance() {
if (uniqueInstance == null) { // 延迟实例化,懒汉式
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
//对方法加锁,同步线程对其的调用
//但是只有第一次执行getInstance时,才真正需要同步,其他时候都是对性能的损耗
public class Singleton2 {
private static Singleton2 uniqueInstance;
private Singleton2() {
}
// 每次都需要同步
public static synchronized Singleton2 getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton2();
}
return uniqueInstance;
}
}
//使用“急切”创建实例(饿汉式),JVM会在线程访问这个静态变量之前,一定先创建这个实例,所以线程安全。
//缺点是不是在需要的时候才创建实例
public class Singleton3 {
private static Singleton3 uniqueInstance = new Singleton3();
private Singleton3() {
}
// 直接使用
public static Singleton3 getInstance() {
return uniqueInstance;
}
}
//利用双重检查加锁,在getInstance方法中减少使用同步,只有第一次会同步
public class Singleton4 {
private static volatile Singleton4 uniqueInstance;
private Singleton4() {
}
// 缩小同步范围
public static synchronized Singleton4 getInstance() {
if (uniqueInstance == null) {//pointA
synchronized(Singleton4.class){
if(uniqueInstance == null)//pointB
uniqueInstance = new Singleton4();
}
}
return uniqueInstance;
}
}
JDK中的单例场景有:
(1)java.lang.Runtime 中的getRuntime();
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
..............
}
(2)java.awt.Desktop 中的getDesktop();
public class Desktop {
private DesktopPeer peer;
/**
* Suppresses default constructor for noninstantiability.
*/
private Desktop() {
peer = Toolkit.getDefaultToolkit().createDesktopPeer(this);
}
public static synchronized Desktop getDesktop(){
if (GraphicsEnvironment.isHeadless()) throw new HeadlessException();
if (!Desktop.isDesktopSupported()) {
throw new UnsupportedOperationException("Desktop API is not " +
"supported on the current platform");
}
sun.awt.AppContext context = sun.awt.AppContext.getAppContext();
Desktop desktop = (Desktop)context.get(Desktop.class);
if (desktop == null) {
desktop = new Desktop();
context.put(Desktop.class, desktop);
}
return desktop;
}