简介
单例模式是一种常用的设计模式,在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象的实例。比如:银行ATM系统对于bank这个类的话,就需要使用单例模式,来保证银行只有一个。
单例模式的优点
由于单例模式只生成一个实例,减少了系统性能的开销,当一个对象的产生需要较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动的时候直接产生一个单例对象,然后永久驻留内存的方式来解决。
单例模式可以在系统设置全局的访问点,优化共享资源访问。例如可以设计一个单例类,负责所有数据表的映射
两大步骤
1.将类的构造方法私有化,保证只能调用该类提供的静态方法来获得该类的唯一实例。
2.在该类中提供一个静态方法,调用该方法时判断该类的实例是否存在,存在就将实例返回,否则就创建该类的实例
单例模式之
饿汉式
public class SingletonDemo1 {
//类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然线程安全
private static SingletonDemo1 instance=new SingletonDemo1();
private SingletonDemo1(){//私有化构造器
}
//方法没有同步,调用效率高
static SingletonDemo1 getInstance(){
return instance;
}
}
测试代码一
public class Client {
public static void main(String[] args) {
SingletonDemo1 s1 = SingletonDemo1.getInstance();
SingletonDemo1 s2 = SingletonDemo1.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);
}
}
运行结果一
通过比较可以两个是同一个对象。饿汉,饿汉!关键就是一个饿字。举个例子,做饭。大早上的就把今天一天要吃的早餐,中餐晚餐都提前做好。在这里的话,虽然暂时不需要使用这个对象,但是提前就创建好了,这就是饿汉式。
懒汉式
public class SingletonDemo2 {
//类初始化时,不初始化这个对象(延时加载,真正用到的时候再创建)
private static SingletonDemo2 instance;
private SingletonDemo2(){
//私有化构造器
}
//方法同步,调用效率低
public static synchronized SingletonDemo2 getInstance(){
if(instance==null){
instance=new SingletonDemo2();
}
return instance;
}
}
测试代码二
public class Client {
public static void main(String[] args) {
SingletonDemo2 s1 = SingletonDemo2.getInstance();
SingletonDemo2 s2 = SingletonDemo2.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);
}
}
测试结果二
同样的通过比较,两个还是同一对象。懒汉,懒汉,关键一个懒字。举个例子,同样也是做饭。还没到中午,但是懒的提前做中餐,等到了中午再做吧。这里的话,懒的创建对象,等用到这个对象的时候再来创建这个对象,这就是懒汉式。
懒汉式与饿汉式相反,可以对比着理解。
静态内部类
public class SingletonDemo3 {
private static class SingletonClassInstance {
private static final SingletonDemo3 instance = new SingletonDemo3();
}
public static SingletonDemo3 getInstance() {
return SingletonClassInstance.instance;
}
private SingletonDemo3() {
}
}
测试代码三
public class Client {
public static void main(String[] args) {
SingletonDemo3 s1 = SingletonDemo3.getInstance();
SingletonDemo3 s2 = SingletonDemo3.getInstance();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);
}
}
测试结果三
静态内部类实现单例模式,这种方式,线程安全,调用效率高,并且也实现了延时加载。
注意:
1.外部并没有static属性,则不会像饿汉式那样立即加载对象
2.只有真正调用getInstance()才会加载静态内部类。加载时是线程安全的。
3.instance是static final 类型,保证了内存中只有这样一个实例存在,而且只能赋值一次,从而保证线程安全
4.兼备了高并发和延时加载的优势
枚举
public enum SingletonDemo4 {
//这个枚举元素,本身就是单例对象
INSTANCE;
//可以添加自己需要的操作
public void singletonOperation(){
//功能以及处理
}
}
测试代码四
public class Client {
public static void main(String[] args) {
System.out.println(SingletonDemo4.INSTANCE == SingletonDemo4.INSTANCE);
}
}
测试结果四
几种单例模式对比
创建10个线程,每个线程执行100000000次,每次都去获得instance对象。
public class Client2 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
int threadNum=10;
final CountDownLatch countDownLatch=new CountDownLatch(threadNum);
for(int i=0;i<threadNum;i++){
new Thread(new Runnable() {
public void run() {
for(int i=0;i<100000000;i++){
Object object=SingletonDemo1.getInstance();
//Object o = SingletonDemo4.INSTANCE;//枚举
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();//main线程阻塞,直到计数器变为0,才会继续往下执行
long end = System.currentTimeMillis();
System.out.println("总共消耗时间:"+(end-start));
}
}
测试结果
饿汉式
懒汉式
静态内部类
枚举
饿汉式:线程安全,调用效率高,不能延时加载
懒汉式:线程安全,调用效率不高,但是可以延时加载
静态内部类式:线程安全,调用效率高,也可以延时加载
枚举式:线程安全,调用效率高,并且可以天然防止反射和反序列化漏洞
如何选用
当 单例对象 占用资源少,不需要延时加载: 枚举式 好于 饿汉式
当 单例对象 占用资源大,需要延时加载: 静态内部类式 好于 懒汉式