您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
浅谈设计模式之单例模式
 
作者:mikyou来源:C博客 发布于 2016-7-27
   次浏览      
 

单例模式,可以说是众多设计模式中的最简单的一种的设计模式。因为它更易于理解和更易于掌握。

一、单例模式最核心的思想:

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();
}
}
}
}

运行结果:

   
次浏览       
相关文章

Java微服务新生代之Nacos
深入理解Java中的容器
Java容器详解
Java代码质量检查工具及使用案例
相关文档

Java性能优化
Spring框架
SSM框架简单简绍
从零开始学java编程经典
相关课程

高性能Java编程与系统性能优化
JavaEE架构、 设计模式及性能调优
Java编程基础到应用开发
JAVA虚拟机原理剖析
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

Java 中的中文编码问题
Java基础知识的三十个经典问答
玩转 Java Web 应用开发
使用Spring更好地处理Struts
用Eclipse开发iPhone Web应用
插件系统框架分析
更多...   

Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术

Struts+Spring+Hibernate/EJB+性能优化
华夏基金 ActiveMQ 原理与管理
某民航公司 Java基础编程到应用开发
某风电公司 Java 应用开发平台与迁移
日照港 J2EE应用开发技术框架与实践
某跨国公司 工作流管理JBPM
东方航空公司 高级J2EE及其前沿技术
更多...