单例模式,是设计模式中最简单的模式之一;这一模式的目的是使得类的一个对象称为系统中该类型的唯一实例,首先看一下它的完整定义:确保一个类只有一个实例,并提供一个全局访问点,下面来看一个经典的单例模式的实现:
public class Singleton {
// 声明一个用来记录Singleton类的唯一实例
private static Singleton uniqueInstance;
// 将构造方法声明为私有的,只有在Singleton类内才可以调用构造器
private Singleton(){ }
// 通过该方法实例化对象,并返回这个实例
public static Singleton getInstance(){
if(null == uniqueInstance){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
通过上述写法,可以得出如下两个结论:
1、我们把这个类设计成自己管理的一个单独实例,同时避免了其他类在自行产生该类的实例。要想取得该类实例,通过这个单例类是唯一的途径;
2、当你需要该单例类的实例时,向类查询,它会返回单个实例;同时上述经典实现利用了延迟实例化的方式创建单例实例,这种做法对于那些对于资源敏感的对象来说特别重要;
虽然上述经典实现确实提供了一种在单线程环境下很好地实现单例模式的方式,换言之,上述实现方式在多线程的情况下,就不能保证在不同线程环境下调用该静态方法返回的是同一个实例,下面通过一个简单的例子论证下:
public class ThreadTest {
public static void main(String[] args) {
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
public void run() {
System.out.println(Singleton.getInstance() + " MyThread1");
System.out.println(Singleton.getInstance() + " MyThread1");
System.out.println(Singleton.getInstance() + " MyThread1");
System.out.println(Singleton.getInstance() + " MyThread1");
}
}
class MyThread2 extends Thread{
public void run() {
System.out.println(Singleton.getInstance() + " MyThread2");
System.out.println(Singleton.getInstance() + " MyThread2");
System.out.println(Singleton.getInstance() + " MyThread2");
System.out.println(Singleton.getInstance() + " MyThread2");
}
}
输出结果如下:
那么,现在问题回到了如何在多线程环境下,实现单例模式,一个很简单的方式就是将Singleton类中的getInstance方法变为同步(synchronized)方法,上述多线程问题就可以轻易地解决了,如下代码所示:
public class Singleton {
// 声明一个用来记录Singleton类的唯一实例
private static Singleton uniqueInstance;
// 将构造方法声明为私有的,只有在Singleton类内才可以调用构造器
private Singleton(){ }
// 通过该方法实例化对象,并返回这个实例
public static synchronized Singleton getInstance(){
if(null == uniqueInstance){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
然后在调用上面的简单测试类就会发现,多线程问题确实解决了,但是试想一下,无论多少次调用该方法都会同步,这样会降低性能,换言之,我们需要只有第一次执行此方法时,才真正需要同步,一旦设置uniqueInstance变量,就不再需要同步该方法了,但是上述写法恰恰之后每次调用该方法,同步都是一种累赘,好的,既然问题明了了,那么我们解决问题的思路就是首先检查是否实例已经创建了,如果尚未创建,才进行同步。否则,则不同步,看修改后的Singleton代码:
public class Singleton {
// 声明一个用来记录Singleton类的唯一实例
// volatile关键字确保,当uniqueInstance被初始化成Singleton实例时,多个线程能正确地处理uniqueInstance变量
private volatile static Singleton uniqueInstance;
// 将构造方法声明为私有的,只有在Singleton类内才可以调用构造器
private Singleton() {
}
// 通过该方法实例化对象,并返回这个实例
public static synchronized Singleton getInstance() {
// 检查实例,如果不存在就进入同步区块
if (null == uniqueInstance) {
synchronized (Singleton.class) {
// 进入同步区块后,再检查一次,如果仍然是null,才创建实例
if (null == uniqueInstance) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
通过这种“双重检查加锁”的方式,可以大大减少getInstance()的时间耗费,以后几种实现单线程的方式都属于延迟实例化的方式,即真正需要后才会实例化该单例类,除了这种方式之外,还有一种“急切”创建实例的方式,先看代码:
public class Singleton {
// 在静态初始化器中创建单例,保证了线程安全
private static Singleton uniqueInstance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return uniqueInstance;
}
}
JVM在加载这个类时,马上创建此唯一的单例实例。JVM保证在任意线程访问uniqueInstance静态变量之前,一定会先创建此实例,这种单例的实现方式适用于应用程序总是创建并使用单例实例,或者在创建或运行时的负担不太重这两种情况;
以上几种实现方式都具有各自的优缺点,至于选择哪种实现方式就具体问题,具体分析,好了,就到这吧!!