生活中很多东西都是独一无二的, 比如每个人都是唯一的, 不可能有第二个自己; 电脑中的任务管理器只能打开一个, 那就是单例, 而QQ能打开多个, 那就是多例. 反映在编程里, 单例就是一个类只允许产生一个对象, 不允许创建多个对象!
说起单例模式, 不得不提到: “饿汉式单例” 和 “懒汉式单例”, 所谓"饿汉"就是程序一运行就迫不及待地产生唯一的对象, 而"懒汉"正好相反, 程序运行起来时不急着创建对象, 直到外界来问它要对象的时候, 才开始创建. 两者各有各的好处, 下面我们一个个分析
/**
* @author cqf
*/
public class Singleton { //饿汉式单例
//构造器私有,防止外界直接创建本类的对象
private Singleton() {}
//产生一个对象
private static Singleton singleton = new Singleton();
//对外暴露一个获取实例的静态方法
public static Singleton getInstance() {
//返回唯一的实例
return Singleton.singleton;
}
}
class SingletonTest {
public static void main(String[] args) {
Singleton instanceOne = Singleton.getInstance(); //问它要一次对象
Singleton instanceTwo = Singleton.getInstance(); //问它再要一次对象
System.out.println(instanceOne==instanceTwo); //比较两次得到的对象是否是同一个
}
}
/**
* @author cqf
*/
public class Singleton { //懒汉式单例
//构造器私有,防止外界直接创建本类的对象
private Singleton() { }
//声明一个对象,但不初始化
private static Singleton singleton = null;
//对外暴露一个获取实例的静态方法
public static Singleton getInstance() {
if (Singleton.singleton == null) {
singleton = new Singleton();
return singleton;
} else {
return Singleton.singleton;
}
}
}
class SingletonTest {
public static void main(String[] args) {
Singleton instanceOne = Singleton.getInstance(); //问它要一次对象
Singleton instanceTwo = Singleton.getInstance(); //问它再要一次对象
System.out.println(instanceOne == instanceTwo); //比较两次得到的对象是否是同一个
}
}
饿汉式单例在一开始就创建对象, 在程序刚开始运行时可能慢一点, 但是后来再拿对象速度就很快了, Spring容器用的也是这种策略; 因为一开始就创建了, 不确定是否用得到这个对象, 如果用不到的话, 会产生资源浪费, 另外它天生是线程安全的, 因为一开始就创建对象的话, 线程不会误操作. 那么懒汉式的优缺点真好反过来
有没有办法把两者的优点集中起来, 并抛弃掉缺点(取其精华, 去其糟粕)? 当然有!那就是懒汉式静态内部类单例
话不多说, 上代码:
/**
* @author cqf
*/
public class Singleton { //懒汉式静态内部类单例
//构造器私有,防止外界直接创建本类的对象
private Singleton() {
//防止反射破坏单例
if (InnerSingletonClass.singleton != null) {
throw new RuntimeException("不允许创建多个实例!");
}
}
//静态内部类
private static final class InnerSingletonClass{
private static final Singleton singleton = new Singleton();
}
//对外暴露一个获取实例的静态方法
public static Singleton getInstance() {
return InnerSingletonClass.singleton;
}
}
class SingletonTest {
public static void main(String[] args) {
Singleton instanceOne = Singleton.getInstance(); //问它要一次对象
Singleton instanceTwo = Singleton.getInstance(); //问它再要一次对象
System.out.println(instanceOne == instanceTwo); //比较两次得到的对象是否是同一个
}
}
利用静态内部类巧妙地节省了资源又保证了线程安全; 原因是静态内部类默认不加载, 所以节省了资源, 至于线程安全问题, 那是由虚拟机保证的:
虚拟机会保证一个类的clinit()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的clinit()方法,其他线程都需要阻塞等待,直到活动线程执行clinit()方法完毕。需要注意的是,其他线程虽然会被阻塞,但如果执行clinit()方法的那条线程退出clinit()方法后,其他线程唤醒后不会再次进入clinit()方法. --《深入理解JVM》