单例模式,可以说是众多设计模式中的最简单的一种的设计模式。因为它更易于理解和更易于掌握。
一、单例模式最核心的思想:
Ensure a class has only one instance,
and provide a global point of access to it.(为了确保一个类只有一个实例(对象),并且为这个对象提供一个全局访问点)
二、个人理解:
就是在整个程序运行的过程中该类的实例只会被创建一次,一旦被创建,以后都不能被创建一个新的实例也即是该类的实例是独一无二的,类似古代的君王独一无二,无可替代的。
三、思考分析:
我们紧紧围绕着它的思想找突破点,它的意思就是保持这个类只能有一个对象。
那么我们如何去确保一个类只能创建一个对象了,也即是保证这个类的对象只能被创建一次???
我们可以这样想:我们都知道要想创建一个对象,最直接最必须的方法就是通过这个构造方法来创建,然后直接通过new关键字就可以,也即是我们创建多次该类对象,就new几次。所以我们必须控制这种构造器泛滥使用,从而保证我们单例模式的类对象只能被创建一次。所以就索性断绝或者说屏蔽了外部直接通过new方式来泛滥或者说任意地创建多个
对象。所以就想到了:
1、控制单例模式下该类的构造方法权限(private 私有化),从而屏蔽了外部泛滥地创建该类的对象权利,切断所有外界创建该类对象途径或者说接口,(因为构造器是创建对象的唯一途径,构造方法加了private修饰词后,外部是没有权限创建该类的对象的)。-------------(关键点一:该类的构造器私有化)
但是这样的话问题来了,因为屏蔽了所有外部创建该类的对象途径,但是我们又需要创建一个唯一的对象实例,那怎么办呢??憋慌,既然外部不能创建,那么我们就从内部开始
创建。
2、既然是独一无二的,那么不是谁都能访问到的,就像古代的皇帝一样,独一无二的,并不是谁想见都能见到的。但是外部的人又必须去向皇帝报告一些事情,他只能间接地通过皇帝身边的人,内部人员间接把信息传达给皇帝。我们的单例模式也是如此外部是不能直接访问该类的对象,所以该类的唯一对象必须私有化(private),还有一个就是static静态的(为什么是static后面解释)外部是不能直接访问该对象的引用的,否则可以访问的话,那么这个对象的引用可以被任意的修改。所以需要给该对象的引用私有化-----(关键点二:设置一个私有的静态的该类对象的引用)
3、那么问题又来了,就是你好不容易在内部创建的一个该类的对象,还被设置成private外部不能访问,我们要能全局被访问???
既然外不能访问的话,我们就直接在内部访问,并且提供一个方法(这个方法就相当于皇帝身边人),但是这个方法必须是全局访问的所以需要static静态的,(到了这里我想你应该明白了为什么该类的对象要静态,因为这个方法是静态的,静态的方法只能访问静态的变量),供外部访问,从而实现了外部间接访问这个对象。一个public权限的static静态方法(为了设置一个全局访问点)来返回这个已经在内部创建好的对象,也就是对于外部而言
暴露这个方法,供外部访问,这也是外部访问该对象的唯一途径(唯一一个访问点)。---------(关键点三:设置一个公有的静态的方法,访问内部对象,并把间接把
该内部对象返回给外部,从而使得外部要得到该对象,只能通过调用该方法,才能访问到该对象。)
四、总结:
1、该类的构造器私有化
2、设置一个私有的静态的该类对象的引用
3、设置一个公有的静态的方法,访问内部对象,并把间接把该内部对象返回给外部,从而使得外部要得到该对象,只能通过调用该方法,才能访问到该对象。
五、两种的单例模式
5.1、饿汉式单例模式
特点:类加载的时候,就对这个类的对象进行创建。
public class HungrySingleton {//饿汉式的单例模式 private HungrySingleton(){} //关键点一:构造器私有化(屏蔽了外部直接使用new关键字泛滥地创建对象) private static HungrySingleton singleton=new HungrySingleton(); //关键点二:设置一个私有的静态的该类对象的引用 (屏蔽了外部直接访问该对象的权限) public static HungrySingleton createSingleton(){ //关键点三:设置一个公有的静态的方法, 访问内部对象,并把间接把该内部对象返回给外部, 从而使得外部要得到该对象,只能通过调用该方法,才能访问到该对象。 return singleton; } } |
5.2、懒汉式单例模式
特点:什么时候需要用到这个类的对象才开始去创建该类的对象
[java] view plain copy print?在CODE上查看代码片派生到我的代码片 public class LazySingleton {//懒汉式单例模式 private LazySingleton(){}//关键点一:构造器私有化 private static LazySingleton singleton=null; //关键点二:该类的对象的静态,私有化 public static LazySingleton createSingleton(){ if (singleton==null) {//如果该对象还没创建,就开始创建 singleton=new LazySingleton(); } return singleton;//如果已存在就直接返回 } } |
六、懒汉式单例模式的改进
为什么要对该单例要改进呢,不是挺好的吗,挺完美的吗?大家可以仔细观察一下上面的代码,是不是 少了什么,如果是在单线程的操作上面是没有问题,但是如果是多线程中就会存在线程同步的问题,如果同时有线程A和线程B同时调用该静态的createSingleton方法,那么if(singleton==null){}语句为真,那么此时线程A和线程B都会创建一个新的该类的对象,所以就不符合但到单例模式的思想。解决办法,就是保证线程A在调用createSingleton方法的时候,其他的线程不能访问该方法,所以需要给这个方法加锁
[java] view plain copy print?在CODE上查看代码片派生到我的代码片 public class LazySingleton {//懒汉式单例模式 private LazySingleton(){}//关键点一:构造器私有化 private static LazySingleton singleton=null;//关键点二:该类的对象的静态,私有化 synchronized public static LazySingleton createSingleton(){ if (singleton==null) {//如果该对象还没创建,就开始创建 singleton=new LazySingleton(); } return singleton;//如果已存在就直接返回 } }<span style="font-size:18px;"> </span> |
七、饿汉式单例模式与懒汉式单例模式区别
1、饿汉式单例模式是在单例类被加载的时候就创建该类的实例,而懒汉式的单例模式则是在第一次引用该类的实例才去创建的
2、从资源内存上看饿汉式单例模式更差点,但是从效率来看的话饿汉式更高,速度和反应时间更快
3、饿汉式在JAVA中很容易实现,但是在C++中却很难实现。GoF在提出单例模式的概念的时候,举例子就是懒汉式的单例模式,所以以至于
在JAVA中单例模式一般都是懒汉式的单例模式,其实实际上,就JAVA语言的特点来说的话,饿汉式的单例模式更合适,因为提高速度和效率,再者对于多线程的JAVA来说,饿汉式的单例模式就轻松避开了线程同步的问题
八、单例模式的优缺点:
1、单例模式的优点:
1.1、单例模式在内存中只有一个实例对象,减少了内存的开支,特别是一个对象需要频繁的创建、销毁,而且创建或销毁的性能又无法优化时可以考虑采用到哪例模式
1.2、由于单例模式只能创建一个实例对象,减少性能开销,当一个对象创建需要较多的资源,如读取配置,产生对其他对象的依赖的时候,可以考虑单例模式,然后用永久驻留内存方式来解决。
1.3、单例模式可以避免对资源的多重占用
1.4、单例模式可以在系统设置全局的访问点,优化和共享资源访问
2、单例模式的缺点:
2.1、无法创建子类,不利于扩展,扩展性差。
2.2、单例模式对测试不利。在测试过程中,并行的开发环境下,如果采用单例模式的类没有测完是不能进行其他的测试。
九、单例模式的应用:
在一个系统中,如果要求一个类有且仅有一个实例,当出现多个实例时就会造成不良反应,则此时可以采用单例模式。
1、要求生成唯一的序列号环境
2、在整个项目中需要一个共享访问点或共享数据,例如,一个web页面的计数器,可以使用单例模式来保持计数器的值更新,并且能确保线程是安全的
3、创建一个对象需要消耗过多的资源,如访问IO,数据库资源等
4、需要定义大量的静态的变量和方法,也可以使用单例模式
十、单例模式使用的过程中应注意什么?
(根据功能,单例模式的类分为两种:一种是有状态的单例类;一种是无状态的单例类)
有状态的单例类:它的对象一般是可变的,通常当做状态库使用,例如:给系统提供唯一的序列号
无状态的单例类:无状态的单例类是不变的,一般提供的是工具性的功能方法。例如IO操作,访问数据库资源
注意点一:单例类仅仅局限于一个JVM,所以当有多个JVM的分布式系统时,这个单例类就会被在多个JVM中实例化,造成多个单例类的对象出现,与单例模式的思想的不符合,若是无状态的单例类则不会有影响,因为无状态的单例类是不变的;但是,如果是有状态的单例类则会出现问题,例如,是给系统生成唯一的序列号,那么会造成系统的序列号不唯一。
注意点二:同一个JVM中会有多个类的加载器,当两个类加载器同时加载同一个类的时候,会出现单例类的两个实例,所以还是得尽量少使用有状态的单例类。
注意点三:在使用单例模式时,我们还需要去关注一点就是序列化(Serializable)和克隆(Cloneable)对实例的唯一性的影响。如果一个单例的类使用
实现Serializable和Cloneable接口,可能被反序列或克隆出一个新的实例,从而破坏了单例模式的思想,所以单例类一般不会去实现这两个接口
十一:应用
应用一:统计访问该单例类的次数
饿汉式的单例模式:
[java] view plain copy print?在CODE上查看代码片派生到我的代码片 public class CountSingleton { private CountSingleton(){} private static CountSingleton mCountSingleton=new CountSingleton(); private int num=0; public static CountSingleton getInstance(){ return mCountSingleton; } public synchronized int getCounts(){//synchronized实现线程同步 return ++num; } } |
[java] view plain copy print?在CODE 上查看代码片派生到我的代码片 <pre class="java" name="code"> <p>package com.mikyou.example;</p><p>public class SingleDemo {</p><p> public static void main(String[] args) { CountThread threadA=new CountThread("A线程"); CountThread threadB=new CountThread("B线程"); threadA.start(); threadB.start(); } } class CountThread extends Thread{ private String threadName; public CountThread (String name) { threadName=name; } @Override public void run() { CountSingleton singleton=CountSingleton.getInstance(); for (int i = 0; i < 5; i++) { System.out.println(threadName+"---> 第"+singleton.getCounts()+"次访问该单例类"); try { sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }</p><pre class="java" name="code">package com.mikyou.example; public class SingleDemo { public static void main(String[] args) { CountThread threadA=new CountThread("A线程"); CountThread threadB=new CountThread("B线程"); threadA.start(); threadB.start(); } } class CountThread extends Thread{ private String threadName; public CountThread (String name) { threadName=name; } @Override public void run() { CountSingleton singleton=CountSingleton.getInstance(); for (int i = 0; i < 5; i++) { System.out.println(threadName+"---> 第"+singleton.getCounts()+"次访问该单例类"); try { sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } |
运行结果:(虽然A、B两个线程运行的顺序不一样,但是它利用单例模式成功实现了在两个线程下,采用饿汉式单例模式每次只允许一个线程去调用该方法,所以进一步体现了饿汉式的单例模式的线程安全)
懒汉式的单例模式(无加锁)
package com.mikyou.example; public class CountSingleton { private CountSingleton(){} private static CountSingleton mCountSingleton=null; private int num=0; public static CountSingleton getInstance(){//无加锁 if (mCountSingleton==null) { mCountSingleton=new CountSingleton(); } return mCountSingleton; } public synchronized int getCounts(){//synchronized实现线程同步 return ++num; } } |
[java] view plain copy print?在CODE上查 看代码片派生到我的代码片 <pre class="java" name="code">package com.mikyou.example; public class SingleDemo { public static void main(String[] args) { CountThread threadA=new CountThread("A线程"); CountThread threadB=new CountThread("B线程"); threadA.start(); threadB.start(); } } class CountThread extends Thread{ private String threadName; public CountThread (String name) { threadName=name; } @Override public void run() { CountSingleton singleton=CountSingleton.getInstance(); for (int i = 0; i < 5; i++) { System.out.println(threadName+"---> 第"+singleton.getCounts()+"次访问该单例类"); try { sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } |
运行结果:
|