06-单例模式
6.1模式定义
单例设计模式(Singleton Pattern),顾名思义,是指确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。需要注意的是,在系统中只有真正有“单一实例”的需求时才可使用。
使用单例模式时,有三个要点:
1)某个类只能有一个实例;
2)该类必须自行创建这个实例;
3)该类必须自行向整个系统提供这个实例。
满足了上面三个特点,才能保证正确使用单例设计模式。
6.2模式分析
我们知道,一个正常的是可以通过new操作符来创建对象的。为什么可以任意创建实例对象呢?因为在类中,即使不显示定义构造方法,也存在一个默认的构造方法,供外部生成对象使用。这样在外部就可以随着创建实例对象。单例设计模式只允许存在一个实例对象,也就是说,在外部是不能随意生成对象实例的,那么如何设计单例模式呢?答案就在于我们需要将类的构造方法显式的声明为private方式,然后定义一个获得实例对象的方法获得单例对象。来看如下单例设计模式的结构图:
在单例设计模式中,在类中存在一个全局共享的对象实例,并且类的构造方法是private声明方式的,外界无法访问,也就是不能使用new操作符来创建对象了。在整个系统中我们获得的Singleton实例对象始终都是一个。
6.3模式实现
6.3.1实现一:使用同步线程安全创建单例对象
在Singleton类中需要做三点处理:
1)含有一个静态私有的共享实例对象;
2)构造方法显式声明为私有方式,即外部不能创建实例对象;
3)含有一个公有获取单例对象的方法,即该类自行向整个系统提供这个实例。
package com.demo.singleton; /** * Created by lsq on 2018/3/15. * 单例设计模式 */ public class Singleton { //类共享实例对象 private static Singleton singleton = null; //私有构造方法 private Singleton(){ System.out.println("-- this is Singleton constructor!!!"); } //获得单例对象的方法 public synchronized static Singleton getInstance(){ //判断共享对象是否为null,如果为null,则new一个新的对象 if (singleton==null){ singleton = new Singleton(); } return singleton; } }
创建客户端程序,首先来看看是否能使用new操作符创建Singleton对象,如下图所示:
上面的尝试会报编译错误,所以单例模式是不能使用new操作符创建实例对象的。我们要通过类中获得单例对象的方法来获得单例对象,如下所示:
import com.demo.singleton.Singleton; /** * Created by lsq on 2018/3/15. * */ public class MainApp { public static void main(String[] args) { //通过提供的外部获得单例对象的接口获得Singleton对象实例 Singleton singleton = Singleton.getInstance(); //获得另外一个对象实例 Singleton singleton2 = Singleton.getInstance(); //比较两个对象是不是同一个对象 if (singleton==singleton2){ System.out.println("--这是同一个对象!"); }else { System.out.println("--这是不同的对象!"); } } }
运行结果如下所示:
注意:这里在获得实例对象的方法前添加了synchronized关键字,这样程序就变成了线程安全的。
6.3.2实现二:创建一个类全局对象实例作为单例对象
如果不想使用synchronized关键字,还有另外一种方法供你选择。
这里与上面一种方法不同的是,首先将全局共享对象实例化,在获得单例对象的方法中,直接返回全局共享对象,而不使用synchronized关键字。
package com.demo.singleton; /** * Created by lsq on 2018/3/15. * 单例设计模式 */ public class Singleton2 { //类共享实例对象实例化 private static Singleton2 singleton = new Singleton2(); //私有构造方法 private Singleton2(){ System.out.println("-- this is Singleton2 constructor!!!"); } //获得单例方法 public static Singleton2 getInstance(){ //直接返回共享对象 return singleton; } }
两种实现方式的比较:
以上两种方式都能产生单例对象实例,两者有各自的优缺点:
方式一:优点是,不会产生内存浪费,因为共享实例对象开始没有被初始化,而是在获得共享对象的方法中动态生成实例的;缺点,也在获得共享对象的方法上,使用synchronized线程同步关键字,执行效率会有所降低。
方式二:缺点,会产生内存浪费,因为在加载Singleton2类时就已经初始化共享对象实例;优点,由于没有synchronized线程同步,执行效率高。
6.3.3提高:多例模式实现
1.多例模式分析方法
在前面,我们已经掌握了单例设计模式的设计方法,其实,在我们的程序设计中,还存在另外一种模式——多例模式。所谓多例模式就是存在多个对象实例,供外部应用调用,比如数据库连接池。下面给出一种比较常用的设计思路,供大家参考。
在多例模式中,使用ArrayList存储多个实例对象,然后使用第二种方式,首先在类中添加N个对象实例,在获得实例对象的方法中产生一个0~N之间的随机数,然后返回ArrayList中指定的随机数位置的对象实例。
静态类图如下所示:
2.多例模式实现——Multipleton
package com.demo.singleton; import java.util.ArrayList; /** * Created by lsq on 2018/3/15. * 多例模式 */ public class Multipleton { //多例数量 private static final int N=10; //存放N个实例对象的容器 private static ArrayList<Multipleton> list = new ArrayList<>(N); //每个对象的序号、标识 private int no; //私有构造方法,防止外界应用程序实例化 private Multipleton(int no){ this.no = no; System.out.println("-- Create Multipleton Object["+no+"]!"); } //实例化N个对象实例 static { //添加Multipleton对象实例 for (int i=0; i<N;i++){ list.add(new Multipleton(i)); } } /** * 随机获得实例对象 */ public static Multipleton getRandomInstance(){ //获得随机数字 int num = (int)(Math.random() * N); //获得list中的对象实例 return list.get(num); } public int getNo() { return no; } public void setNo(int no) { this.no = no; } }
注:使用static关键字向list数组中放入N个Multipleton对象实例,使创建对象实例过程静态化,在类加载的时候执行一次,而创建对象实例的时候则不会执行。
私有构造方法中,增加一个传入的参数,用来标识对象实例。
getRandomInstance方法中,首先产生一个0~N的随机数,然后返回list数组指定位置的实例对象。
3.创建MultipletonClient客户端类
package com.demo.singleton; /** * Created by lsq on 2018/3/15. * 客户端应用程序 */ public class MultipletonClient { public static void main(String[] args) { //获得Multipleton对象实例 Multipleton multipleton = Multipleton.getRandomInstance(); System.out.println("multipleton:"+multipleton.getNo()); //再次获得Multipleton对象实例 Multipleton multipleton2 = Multipleton.getRandomInstance(); System.out.println("multipleton2:"+multipleton2.getNo()); //比较两个对象是否是同一个对象实例 if (multipleton==multipleton2){ System.out.println("--这是同一个对象!"); }else { System.out.println("--这是不同的对象!"); } } }
运行结果如下:
从上面的执行结果,可以得出两个结论:
1)实例化N个实例对象的过程只执行了一次。
2)随机获得了Multipleton对象。
6.4使用场合
1)当在系统中某个特定的类对象实例只需要一个的时候,可以使用单例设计模式。需要注意的是,只有真正有“单一实例”的需求时才可使用。
2)多例模式的实现原理:首先,在类中设置一个存储多个实例的容器,如ArrayList或HashMap;然后,静态初始化实例对象并添加到容器中;最后根据条件返回容器中的实例对象。