概念
单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中应用该模式的类只有一个实例。即一个类只有一个对象实例。
实现
第一步:将类的构造函数声明为私有的构造函数,这样我们在外部就无法通过new来实例化该类,这样可以保证类的实例只有一个(那就是在类的内部给它实例化)。
第二步:在类的里面声明一个公开的全局的静态字段(其实也就是提供一个全局的访问点),外部对象通过这个访问点可以拿到该类的唯一实例。切记,该字段必须声明成static静态段,如果声明成非静态字段,那在类的外部就访问不到了。因为非静态成员只能通过类的实例去访问,而我们在类的外部是无法对该类进行实例化的。
懒汉式单例类
class SingleInstance {
private static SingleInstance instance=null;
//在接口被调用的时候创建对象
public static SingleInstance getInstance(){
if(instance==null){
instance=new SingleInstance ();
}
return instance;
}
//构造器私有化
private SingleInstance(){
}
}
小结:该种方式优点在于调用接口时才实例化对象,所以节约内存资源,但是却存在线程安全的问题。
线程安全的懒汉式单例(双重锁)
public class SingleInstance {
private volatile static SingleInstance single;
//构造器私有化
private SingleInstance(){};
//对外接口
public static SingleInstance getSingle(){
if(single==null){
synchronized(SingleInstance.class){
if(single==null){//二次检查
single=new SingleInstance();
}
}
}
return single;
}
}
测试类:
public class Test9 implements Runnable{
public static void main(String[] args) {
//同时启动两个线程进行访问
Test9 test=new Test9();
Thread t=new Thread(test);
Thread t1=new Thread(test);
t.start();
t1.start();
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<10;i++){
SingleInstance s=SingleInstance.getSingle();
System.out.println(s);
}
}
}
输出结果:
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
小结:该种方式只在某些重要的代码上加锁即保证了线程安全又提高了效率,但是在《java并发编程实战》中对此种方式并不是很推崇,原因是这种双重检查加锁的机制(DCL)之所以出现是由于早期的jvm同步开销很大,如果不加volatile变量,该种方式会导致线程看到一个未被完全构造的对象,也就是会读取到一个无效对象,不管怎样,该种方法已经被废弃,所以还是尽量不要使用(在《java并发编程实战》第16.2.4节有详细介绍,有兴趣的可以再看看)。
饿汉式单例类
public class SingleInstance {
private static SingleInstance s;
//利用静态初始化生成类实例
static{
s=new SingleInstance();
}
//构造方法私有化,防止外部实例化对象
private SingleInstance(){};
//提供公共访问接口
public static SingleInstance getS(){
return s;
}
}
测试输出结果:
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
cn.zy.test.SingleInstance@4eb09321
小结:该种方式的优点在于对象是在类被加载后和线程使用之前被创建,因此不需要考虑线程安全的问题,缺点是提前占用系统资源,当然对于如今的jvm来说,这点开销不算什么。
总结
单例模式虽然简单但是却最常用,稍有不慎却容易出错,对于以上两种方案个人偏向于第二种实现,虽然提前耗费点资源,但代码却变得简洁,由于不用考虑并发因此该种方法不易出错。