软件设计模式之单例模式(JAVA)

什么是单例模式?

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

 

单例模式的特点:

1、单例类有且只能有一个实例。

2、单例类需要自己创建一个自己的实例。

3、单例类需要为其他类提供这个实例。

 

哪些地方经常用到单例?

在计算机系统中,配置文件,线程池,缓存,日志对象,打印机等经常用到单例模式

所谓“一山不容二虎”,如果出现二虎很容易会出问题,比如配置文件,它终究只是一个文件,如果同时有好几个实例访问它并执行修改操作,那么这时候就会引发出一系列的问题 

单例相对于多实例对象也更节约系统资源

 

单例模式常用的有几种模式?

一般来讲单例模式有三种,分别是:懒汉式,饿汉式,登记式。

 

下面直接上代码说明吧

①一个类之所以能够创建出实例是因为有构造方法的存在,只要我们把构造方法的访问修饰符改成私有(private),外界就不能通过new来创建该类的实例。

②在单例类中自身new出一个对象,因为要被外界访问,我们可以把它静态化(static),以便外界访问(类型.对象)

③有时候我们需要控制这个对象,也处于安全起见,我们可以把继续私有化(private),然后提供一个getter方法以便外界访问

SimpletonDemo1.java(单例类)

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonDemo1 {
 4     //将构造方法私有化,阻止外界直接创建对象
 5     private SimpletonDemo1() {
 6     }
 7     //提供static以便外界访问
 8     private static SimpletonDemo1 instance = new SimpletonDemo1();
 9 
10     //提供getter方法以便外界访问私有化对象,static SimpleDemo1返回类型
11     public static SimpletonDemo1 getInstance() {
12         return instance;
13     }
14 }

SimpletonTest.java(测试类)

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonTest {
 4     public static void main(String[] args) {
 5         SimpletonDemo1 s1=SimpletonDemo1.getInstance();
 6         SimpletonDemo1 s2=SimpletonDemo1.getInstance();
 7         
 8         if(s1==s2){//检测对象的内存地址是否一致
 9             System.out.println("s1和s2是同个对象");
10         }else{
11             System.out.println("s1和s2不是同个对象");
12         }
13         
14     }
15 }

效果如下:

 上面所说的就是单例模式里饿汉模式,为什么叫饿汉模式呢?

由于这个实例是被static所修饰,被static修饰的成员属于类所有,当类加载的时候,这个成员就被加载了,也就是说不管外界是否调用这个类,它都已经被加载了。

看起来好像是饿汉,不管三七二十一,先吃了你再说。

 

下面再来看下单例模式中的懒汉模式

就字面上的意思,其实已经很明白了“懒汉模式”,顾名思义不同于饿汉模式,既然饿汉模式是不管三七二十一先吃了再说,那么懒汉模式当然就没那么勤快了,应该是被我们调用后的时候才去实例化对象。

它们的写法很类似,只不过是在用private static声明对象的时候不直接new对象,而是在gette方法里再去实例化对象

然后判断下这个对象是否为null,如果为null则实例化一个对象,如果不会空则直接返回对象。

下面看下具体代码

SimpletonDemo2.java(单例类)

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonDemo2 {
 4     //将构造方法私有化,阻止外界直接创建对象
 5     private SimpletonDemo2() {
 6     }
 7     //提供static以便外界访问
 8     private static SimpletonDemo2 instance;
 9 
10     //提供getter方法以便外界访问私有化对象,static SimpleDemo1返回类型
11     public static SimpletonDemo2 getInstance() {
12         if(instance==null){
13             return instance=new SimpletonDemo2();
14         }else{
15             return instance;
16         }
17     }
18 }

 

SimpletonTest.java(测试类)

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonTest {
 4     public static void main(String[] args) {
 5         SimpletonDemo1 s1=SimpletonDemo1.getInstance();
 6         SimpletonDemo1 s2=SimpletonDemo1.getInstance();
 7         
 8         if(s1==s2){//检测对象的内存地址是否一致
 9             System.out.println("s1和s2是同个对象");
10         }else{
11             System.out.println("s1和s2不是同个对象");
12         }
13         
14         
15         SimpletonDemo2 s3=SimpletonDemo2.getInstance();
16         SimpletonDemo2 s4=SimpletonDemo2.getInstance();
17         
18         if(s3==s4){//检测对象的内存地址是否一致
19             System.out.println("s3和s4是同个对象");
20         }else{
21             System.out.println("s3和s4不是同个对象");
22         }
23         
24     }
25 }

效果如下:

 

总结下两种模式的区别:

1、饿汉式,在加载类的时候比较慢,由于它还要去实例化一个对象并造成内存资源的浪费,但在运行调用中的速度会比较快。

2、懒汉式,在加载类的时候比较快,由于在加载类的时候不需要去实例化对象,但在运行调用时的速度比较慢,由于还要去做判断。

还有一点很重要的是,饿汉模式是属于线程安全,而懒汉模式属于线程不安全,在高并发时会出现问题。

 

那有没有更好的方式呢?答案肯定是有的,我们可以结合了饿汉式和懒汉式各自的优点,不在加载类的时候实例化不必要的对象,又同时具备了线程安全

说直白的点就是利用内部类去静态实例化这个对象,由于内部类需要在外部类被调用的时候才会去加载,也就是说不会在没必要的时候去加载对象从而导致系统资源的浪费,同时我们在内部类中对这个对象实行静态实例化也就避免了线程安全这个问题。

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonDemo {
 4     // 将构造方法私有化,阻止外界直接创建对象
 5     private SimpletonDemo() {
 6     }
 7 
 8     // 定义一个内部类用来实例化对象
 9     private static class Inner_SimpletonDemo {
10         private static SimpletonDemo instance = new SimpletonDemo();
11     }
12 
13     // 提供getter方法以便外界访问私有化对象,static SimpleDemo1返回类型
14     public static SimpletonDemo getInstance() {
15         return Inner_SimpletonDemo.instance;
16     }
17 }

 

再来看下最后一种实现方式,登记式

由于懒汉式和饿汉式都把构造方法私有化了,所以它不能被继承,登记式可以解决这个问题

登记式单例模式  类似于Spring里面的用法,将类名注册并放到Map集合里,下次要用的时候直接取

登记式实际对一组单例模式进行的维护,主要是在数量上的扩展,通过map我们把单例存进去,这样在调用时,先判断该单例是否已经创建,是的话直接返回,不是的话创建一个登记到map中,再返回。对于数量又分为固定数量和不固定数量的。下面采用的是不固定数量的方式,在getInstance方法中加上参数(string name),然后通过子类继承,重写这个方法将name传进去。

SimpletonDemo3.java(单例类)

 1 package com.lcw.simpleton;
 2 
 3 import java.util.HashMap;
 4 import java.util.Map;
 5 
 6 public class SimpletonDemo3 {
 7     // 私有化构造器,保护钩子
 8     private SimpletonDemo3() {
 9     }
10 
11     private static Map<String, SimpletonDemo3> map = new HashMap<String, SimpletonDemo3>();
12     //静态代码块
13     static {
14         SimpletonDemo3 instance = new SimpletonDemo3();
15         map.put(instance.getClass().getName(), instance);
16     }
17 
18     // 参数name为类名
19     public static SimpletonDemo3 getInstance(String name) {
20         if (name == null) {
21             name = "com.lcw.simpleton.SimpletonDemo3";
22         }
23         if (!map.containsKey(name)) {
24             try {
25                 map.put(name, (SimpletonDemo3) Class.forName(name)
26                         .newInstance());
27             } catch (InstantiationException e) {
28                 e.printStackTrace();
29             } catch (IllegalAccessException e) {
30                 e.printStackTrace();
31             } catch (ClassNotFoundException e) {
32                 e.printStackTrace();
33             }
34         }
35         return map.get(name);
36     }
37 
38 }

SimpletonTest.java(测试类)

 1 package com.lcw.simpleton;
 2 
 3 public class SimpletonTest {
 4     public static void main(String[] args) {
 5         SimpletonDemo1 s1 = SimpletonDemo1.getInstance();
 6         SimpletonDemo1 s2 = SimpletonDemo1.getInstance();
 7 
 8         if (s1 == s2) {// 检测对象的内存地址是否一致
 9             System.out.println("s1和s2是同个对象");
10         } else {
11             System.out.println("s1和s2不是同个对象");
12         }
13 
14         SimpletonDemo2 s3 = SimpletonDemo2.getInstance();
15         SimpletonDemo2 s4 = SimpletonDemo2.getInstance();
16 
17         if (s3 == s4) {// 检测对象的内存地址是否一致
18             System.out.println("s3和s4是同个对象");
19         } else {
20             System.out.println("s3和s4不是同个对象");
21         }
22 
23         SimpletonDemo3 s5 = SimpletonDemo3.getInstance("com.lcw.simpleton.SimpletonDemo3");
24         SimpletonDemo3 s6 = SimpletonDemo3.getInstance("com.lcw.simpleton.SimpletonDemo3");
25         if (s5 == s6) {// 检测对象的内存地址是否一致
26             System.out.println("s5和s6是同个对象");
27         } else {
28             System.out.println("s5和s6不是同个对象");
29         }
30     }
31 }

效果图:

 

 

 

作者:Balla_兔子
出处:http://www.cnblogs.com/lichenwei/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!旁边有“推荐”二字,你就顺手把它点了吧,相得准,我分文不收;相不准,你也好回来找我!

posted @ 2014-08-31 10:28  李晨玮  阅读(2102)  评论(7编辑  收藏  举报