Java设计模式之(一)------单例模式

时间:2022-08-24 09:51:12

1、什么是单例模式?

  采取一定的办法保证在整个软件系统中,单例模式确保对于某个类只能存在一个实例。有如下三个特点:

  ①、单例类只能有一个实例

  ②、单例类必须自己创建自己的实例

  ③、单例类必须提供外界获取这个实例的方法

2、单例类的设计思想(Singleton)

  ①、外界不能创建这个类的实例,那么必须将构造器私有化。

public class Singleton {
//构造器私有化
private Singleton(){ } }

  ②、单例类必须自己创建自己的实例,不能允许在类的外部修改内部创建的实例,所以将这个实例用 private 声明。为了外界能访问到这个实例,我们还必须提供 get 方法得到这个实例。因为外界不能 new 这个类,所以我们必须用 static 来修饰字段和方法。

//在类的内部自己创建实例
private static Singleton singleton = new Singleton(); //提供get 方法以供外界获取单例
public Singleton getInstance(){
return singleton;
}

 

3、单例模式之饿汉模式

public class Singleton {
//构造器私有化
private Singleton(){ }
//在类的内部自己创建实例
private static Singleton singleton = new Singleton(); //提供get 方法以供外界获取单例
public static Singleton getInstance(){
return singleton;
} }

  测试:

public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1.equals(s2)); //true
}

  

  这种模式避免了多线程的同步问题,不过在 类装载的时候就进行了实例化,有可能这个实例化过程很长,那么就会加大类装载的时间;有可能这个实例现阶段根本用不到,那么创建了这个实例,也会浪费内存。没有达到 lazy-loading 的效果。

4、单例模式之懒汉模式(线程不安全)

//懒汉模式
public class Singleton {
//构造器私有化
private Singleton(){ }
//在类的内部自己创建实例的引用
private static Singleton singleton = null; //提供get 方法以供外界获取单例
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
} }

  这种方法达到了 lazy-loading 的效果,即我们在第一次需要得到这个单例的时候,才回去创建它的实例,以后再需要就可以不用创建,直接获取了。但是这种设计在多线程的情况下是不安全的。

 Java设计模式之(一)------单例模式

  我们可以创建两个线程来看看这种情况:

public class ThreadSingleton extends Thread{
@Override
public void run() {
try {
System.out.println(Singleton.getInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ThreadSingleton s1 = new ThreadSingleton();
s1.start(); //com.ys.pattern.Singleton@5994a1e9 ThreadSingleton s2 = new ThreadSingleton();
s2.start(); //com.ys.pattern.Singleton@40dea6bc
}
}

  很明显:最后输出结果的两个实例是不同的。这便是线程安全问题。那么怎么解决这个问题呢?

  参考这篇博客:Java多线程同步:http://www.cnblogs.com/ysocean/p/6883729.html

5、单例模式之懒汉模式(线程安全)

  这里我们采用同步代码块来达到线程安全

//懒汉模式线程安全
public class Singleton {
//构造器私有化
private Singleton(){ }
//在类的内部自己创建实例的引用
private static Singleton singleton = null; //提供get 方法以供外界获取单例
public static Singleton getInstance() throws Exception{
synchronized (Singleton.class) {
if(singleton == null){
singleton = new Singleton();
}
}
return singleton;
} }

  

6、单例模式之懒汉模式(线程安全)--双重校验锁

  分析:上面的例子我们可以看到,synchronized 其实将方法内部的所有语句都已经包括了,每一个进来的线程都要单独进入同步代码块,判断实例是否存在,这就造成了性能的浪费。那么我们可以想到,其实在第一次已经创建了实例的情况下,后面再获取实例的时候,可不可以不进入这个同步代码块?

  

//懒汉模式线程安全--双重锁校验
public class Singleton {
//构造器私有化
private Singleton(){ }
//在类的内部自己创建实例的引用
private static Singleton singleton = null; //提供get 方法以供外界获取单例
public static Singleton getInstance() throws Exception{
if(singleton == null){
synchronized (Singleton.class) {
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
} }

  以上的真的完美解决了单例模式吗?其实并没有,请看下面:

7、单例模式之最终版

  我们知道编译就是将源代码翻译成机械码的过程,而Java虚拟机的目标代码不是本地机器码,而是虚拟机代码。编译原理里面有个过程是编译优化,就是指在不改变原来语义的情况下,通过调整语句的顺序,来让程序运行的更快,这个过程称为 reorder。

  JVM 只是一个标准,它并没有规定有关编译器优化的内容,也就是说,JVM可以*的实现编译器优化。

  那么我们来再来考虑一下,创建一个变量需要哪些步骤?

    ①、申请一块内存,调用构造方法进行初始化

    ②、分配一个指针指向该内存

  而这两步谁先谁后呢?也就是存在这样一种情况:先开辟一块内存,然后分配一个指针指向该内存,最后调用构造方法进行初始化。

  那么针对单例模式的设计,就会存在这样一个问题:线程 A 开始创建 Singleton 的实例,此时线程 B已经调用了 getInstance的()方法,首先判断 instance 是否为 null。而我们上面说的那种模型, A 已经把 instance 指向了那块内存,只是还没来得及调用构造方法进行初始化,因此 B 检测到 instance 不为 null,于是直接把  instance 返回了。那么问题出现了:尽管 instance 不为 null,但是 A 并没有构造完成,就像一套房子已经给了你钥匙,但是里面还没有装修,你并不能住进去。

  解决方案:使用 volatile 关键字修饰 instance

  我们知道在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。

  volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

//懒汉模式线程安全--volatile
public class Singleton {
//构造器私有化
private Singleton(){ }
//在类的内部自己创建实例的引用
private static volatile Singleton singleton = null; //提供get 方法以供外界获取单例
public static Singleton getInstance() throws Exception{
if(singleton == null){
synchronized (Singleton.class) {
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
} }

 

到此我们完美的解决了单例模式的问题。但是 volatile  关键字是 JDK1.5 才有的,也就是 JDK1.5 之前是不能这样用的

PS:我们还可以使用 枚举类型 或静态内部类来实现单例模式

8、单例模式之枚举类

public enum Singleton{
INSTANCE;
private Singleton(){}
}

  

9、单例模式之静态内部类

public class InnerSingleton {
private InnerSingleton(){}
public static InnerSingleton getInstance(){
return Inner.instance;
}
static class Inner{
static InnerSingleton instance = new InnerSingleton();
} public static void main(String [] args){
System.out.println(InnerSingleton.getInstance()==InnerSingleton.getInstance());//true
System.out.println(InnerSingleton.getInstance().equals(InnerSingleton.getInstance()));//true }

单例模式的应用:

  1、windows 系统的回收站,我们能在任何盘符删除数据,但是最后都是到了回收站中

  2、网站的计数器,不过不采用单例模式,很难实现同步

  3、数据库连接池,可以节省打开或关闭数据库连接所引起的效率损耗,用单例模式来维护,可以大大降低这种损耗。

由上可以总结单例模式的应用场景:

  ①、资源共享

  ②、方便资源互相通信  

 

Java设计模式之(一)------单例模式的更多相关文章

  1. Java设计模式之《单例模式》及应用场景

    摘要: 原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6510196.html 所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该 ...

  2. Java设计模式之【单例模式】

    Java设计模式之[单例模式] 何为单例 在应用的生存周期中,一个类的实例有且仅有一个 当在一些业务中需要规定某个类的实例有且仅有一个时,就可以用单例模式 比如spring容器默认初始化的实例就是单例 ...

  3. Java设计模式中的单例模式

    有时候在实际项目的开发中,我们会碰到这样一种情况,该类只允许存在一个实例化的对象,不允许存在一个以上的实例化对象,我们将这种情况称为Java设计模式中的单例模式.设计单例模式主要采用了Java的pri ...

  4. 重学 Java 设计模式:实战单例模式

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 5个创建型模式的最后一个 在设计模式中按照不同的处理方式共包含三大类:创建型模式.结 ...

  5. Java设计模式4:单例模式

    前言 非常重要,单例模式是各个Java项目中必不可少的一种设计模式.本文的关注点将重点放在单例模式的写法以及每种写法的线程安全性上.所谓"线程安全性"的意思就是保证在创建单例对象的 ...

  6. 10.Java设计模式 工厂模式,单例模式

    Java 之工厂方法和抽象工厂模式 1. 概念 工厂方法:一抽象产品类派生出多个具体产品类:一抽象工厂类派生出多个具体工厂类:每个具体工厂类只能创建一个具体产品类的实例. 即定义一个创建对象的接口(即 ...

  7. Java设计模式探讨之单例模式

    单例模式是在平时的项目开发中比较常见的一种设计模式,使用比较普遍,网上的资料也是一抓一大把,小Alan也来凑凑热闹,为以后充实点设计模式相关的内容做个简单的开篇. 单例模式是一种创建对象的模式,用于产 ...

  8. Java设计模式学习01——单例模式(转)

    原地址:http://blog.csdn.net/xu__cg/article/details/70182988 Java单例模式是一种常见且较为简单的设计模式.单例模式,顾名思义一个类仅能有一个实例 ...

  9. 【java设计模式】-04单例模式

    单例模式 定义: 确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 类型: 创建类模式 类图: 单例模式特点 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单 ...

  10. Java设计模式09:单例模式的强化(控制实例个数n)

    1. 单例模式的本质: 控制实例数目(目的节约资源) 2. 单例模式体现的一些思想: (1)延迟装载(Lazy Load):懒汉式 (2)缓存:饿汉式 3. 单例模式的变形使用: 控制使用实例个数为3 ...

随机推荐

  1. 用js触发CSS3-transition过渡动画

    用js触发CSS3-transition过渡动画 经过这几天的工作,让我进一步的了解到CSS3的强大,原本许多需要js才能实现的动画效果,现在通过CSS3就能轻易实现了,但是CSS3也有自身的不足,例 ...

  2. rhel7报错整理

    报错现象1: 安装过程需要一段时间,等待完成.   重启后进程     输入 q   再次输入 yes   重启后进入           报错现象2:     挂载镜像后,系统一重启报错:   来自 ...

  3. WebSocket实战之————GatewayWorker使用笔记例子

    参考文档:http://www.workerman.net/gatewaydoc/ 目录结构 ├── Applications // 这里是所有开发者应用项目 │ └── YourApp // 其中一 ...

  4. atitit.web的动态include 跟变量传递 java .net php

    atitit.web的动态include 跟变量传递 java .net php 1. 动态include  <jsp:include 1 2. 使用QueryString 1 3. 使用Ses ...

  5. 学习练习 java数据库查询小题

    10. 查询Score表中的最高分的学生学号和课程号.(子查询或者排序) 11. 查询每门课的平均成绩. 12.查询Score表中至少有5名学生选修的并以3开头的课程的平均分数. 13.查询分数大于7 ...

  6. SSH远程登录密码尝试

    import threading #创建一个登陆日志,记录登陆信息 paramiko.util.log_to_file('paramiko.log') client = paramiko.SSHCli ...

  7. &lpar;转载&comma;但不知道谁原创&rpar;获取SPRING 代理对象的真实实例&comma;可以反射私有方法&comma;便于测试

    /** * 获取 目标对象 * @param proxy 代理对象 * @return * @throws Exception */ public static Object getTarget(Ob ...

  8. sonar安装

    ##jdk不要用yum下载的 一.下载sonar源码 cd /usr/local/src wget https://sonarsource.bintray.com/Distribution/sonar ...

  9. JavaScript中的this指向规则

    首先,JavaScript的this指向问题并非传说中的那么难,不难的是机制并不复杂,而被认为不好理解的是逻辑关系和容易混淆的执行上下文.这篇博客也就会基于这两个不好理解的角度来展开,如要要严格的来对 ...

  10. Saltstack学习之二:target与模块方法的运行

    对象的管理 saltstack系统中我们的管理对象叫做target,在master上我们可以采用不同的target去管理不同的minion,这些target都是通过去管理和匹配minion的id来做的 ...