常见的单例模式有两种创建方式:饿懒汉式与饿汉式
(1)懒汉式
何为懒?顾名思义,就是不做事,这里也是同义,懒汉式就是不在系统加载时就创建类的单例,而是在第一次使用实例的时候再创建。
public class lanhandanli {
//定义一个私有类变量来存放单例,私有的目的是指外部无法直接获取这个变量,而要使用提供的公共方法来获取
private static String dl = null;
//定义私有构造器,表示只在类内部使用,亦指单例的实例只能在单例类内部创建
private String lanhandanli(int a) {
if (a == 1) {
return "sss";
}
return "aaa";
}
//定义一个公共的公开的方法来返回该类的实例,由于是懒汉式,需要在第一次使用时生成实例,所以为了线程安全,使用synchronized关键字来确保只会生成单例
public static synchronized String getInstance(int a) {
if (dl == null) {
dl = new lanhandanli().lanhandanli(a);
}
return dl;
}
public static void main(String[] args) {
System.out.print(getInstance(1) + "11111"+"\n");
System.out.print(getInstance(2) + "22222"+"\n");
System.out.print(getInstance(3) + "33333"+"\n");
}
}
备注:
synchronized
关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。它包括两种用法:synchronized 方法和 synchronized 块。
Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。
(2)饿汉式
又何为饿?饿者,饥不择食;但凡有食,必急食之。此处同义:在加载类的时候就会创建类的单例,并保存在类中
public class ehandanli {
//此处定义类变量实例并直接实例化,在类加载的时候就完成了实例化并保存在类中
private static ehandanli dl = new ehandanli();
//定义无参构造器,用于单例实例
private String ehandanli() {
return "1";
}
//定义公开方法,返回已创建的单例
public static ehandanli getInstance() {
return dl;
}
public static void main(String[] args){
System.out.print(ehandanli.getInstance());
}
}
2、双重加锁机制
public class SLHanDanli { private static volatile SLHanDanli dl = null; private SLHanDanli(){} public static SLHanDanli getInstance(){ if(dl == null){ synchronized (SLHanDanli.class) { if(dl == null){ dl = new SLHanDanli(); } } } return dl; } }
双重指的的双重判断,而加锁单指那个synchronized,第一重判断,如果单例已经存在,那么就不再需要进行同步操作,而是直接返回这个实例,如果没有创建,才会进入同步块,同步块的目的与之前相同,目的是为了防止有两个调用同时进行时,导致生成多个实例,有了同步块,每次只能有一个线程调用能访问同步块内容,当第一个抢到锁的调用获取了实例之后,这个实例就会被创建,之后的所有调用都不会进入同步块,直接在第一重判断就返回了单例
3、类级内部类方式
饿汉式会占用较多的空间,因为其在类加载时就会完成实例化,而懒汉式又存在执行速率慢的情况,双重加锁机制呢?又有执行效率差的毛病,有没有一种完美的方式可以规避这些毛病呢?
貌似有的,就是使用类级内部类结合多线程默认同步锁,同时实现延迟加载和线程安全。
public class ClassInnerClassDanli { public static class DanliHolder{ private static ClassInnerClassDanli dl = new ClassInnerClassDanli(); } private ClassInnerClassDanli(){} public static ClassInnerClassDanli getInstance(){ return DanliHolder.dl; } }
如上代码,所谓类级内部类,就是静态内部类,这种内部类与其外部类之间并没有从属关系,加载外部类的时候,并不会同时加载其静态内部类,只有在发生调用的时候才会进行加载,加载的时候就会创建单例实例并返回,有效实现了懒加载(延迟加载),至于同步问题,我们采用和饿汉式同样的静态初始化器的方式,借助JVM来实现线程安全。
其实使用静态初始化器的方式会在类加载时创建类的实例,但是我们将实例的创建显式放置在静态内部类中,它会导致在外部类加载时不进行实例创建,这样就能实现我们的双重目的:延迟加载和线程安全。
4、使用
在Spring中创建的Bean实例默认都是单例模式存在的。