Java设计模式之(一)------单例模式
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 的效果,即我们在第一次需要得到这个单例的时候,才回去创建它的实例,以后再需要就可以不用创建,直接获取了。但是这种设计在多线程的情况下是不安全的。

我们可以创建两个线程来看看这种情况:
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设计模式之(一)------单例模式的更多相关文章
- Java设计模式之《单例模式》及应用场景
摘要: 原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6510196.html 所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该 ...
- Java设计模式之【单例模式】
Java设计模式之[单例模式] 何为单例 在应用的生存周期中,一个类的实例有且仅有一个 当在一些业务中需要规定某个类的实例有且仅有一个时,就可以用单例模式 比如spring容器默认初始化的实例就是单例 ...
- Java设计模式中的单例模式
有时候在实际项目的开发中,我们会碰到这样一种情况,该类只允许存在一个实例化的对象,不允许存在一个以上的实例化对象,我们将这种情况称为Java设计模式中的单例模式.设计单例模式主要采用了Java的pri ...
- 重学 Java 设计模式:实战单例模式
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 5个创建型模式的最后一个 在设计模式中按照不同的处理方式共包含三大类:创建型模式.结 ...
- Java设计模式4:单例模式
前言 非常重要,单例模式是各个Java项目中必不可少的一种设计模式.本文的关注点将重点放在单例模式的写法以及每种写法的线程安全性上.所谓"线程安全性"的意思就是保证在创建单例对象的 ...
- 10.Java设计模式 工厂模式,单例模式
Java 之工厂方法和抽象工厂模式 1. 概念 工厂方法:一抽象产品类派生出多个具体产品类:一抽象工厂类派生出多个具体工厂类:每个具体工厂类只能创建一个具体产品类的实例. 即定义一个创建对象的接口(即 ...
- Java设计模式探讨之单例模式
单例模式是在平时的项目开发中比较常见的一种设计模式,使用比较普遍,网上的资料也是一抓一大把,小Alan也来凑凑热闹,为以后充实点设计模式相关的内容做个简单的开篇. 单例模式是一种创建对象的模式,用于产 ...
- Java设计模式学习01——单例模式(转)
原地址:http://blog.csdn.net/xu__cg/article/details/70182988 Java单例模式是一种常见且较为简单的设计模式.单例模式,顾名思义一个类仅能有一个实例 ...
- 【java设计模式】-04单例模式
单例模式 定义: 确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 类型: 创建类模式 类图: 单例模式特点 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单 ...
- Java设计模式09:单例模式的强化(控制实例个数n)
1. 单例模式的本质: 控制实例数目(目的节约资源) 2. 单例模式体现的一些思想: (1)延迟装载(Lazy Load):懒汉式 (2)缓存:饿汉式 3. 单例模式的变形使用: 控制使用实例个数为3 ...
随机推荐
- makefile 中=与:=的差别
1."=" make会将整个makefile展开后,再决定变量的值.也就是说,变量的值将会是整个makefile中最后被指定的值.看例子: x = foo y = $(x) bar ...
- 工厂模式(Factory Method)
1.工厂方法模式(Factory Method) 工厂方法模式分为三种: 1-1.普通工厂模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建. 举例如下:(我们举一个发送邮件和短信的例子 ...
- HDU 5504 GT and sequence 模拟
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5504 思路:模拟 代码: #include<stdio.h>//------杭电5504 ...
- centos 下安装pptp (vpn) 的方法
废话少说 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 3 ...
- Yacc 与 Lex 快速入门(词法分析和语法分析)
我们知道,高级语言,一般的如c,Java等是不能直接运行的,它们需要经过编译成机器认识的语言.即编译器的工作. 编译器工作流程:词法分析.语法分析.语义分析.IR(中间代码,intermediate ...
- How to get table pg_stat_user_functions.
修改配置文件postgres.conf track_functions = all # none, pl, all 或者 在当前事物中设置 postgres=# s ...
- mac mono Fiddler.exe启动失败
mono version 5.4 fiddler version 4.6 启动fiddler:mono Fiddler.exe 启动报错 WARNING: The Carbon driver has ...
- java poi 导入日期为空
如上两图,如果是第一种的话,可以导入,,但,如果是第二种的话,导入为空查看,导入的文件,有这么一条 debugger发现 它把2017-11-01转为Double,转不了,出错了,所以,我在catch ...
- ActiveMQ基本详解与总结
MQ简介: MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过写和检索出入列队的针对应用程序的数据(消息)来通信,而无需专用连接来链接它们.消息传 ...
- HDU 1671 Phone List (Trie)
pid=1671">Phone List Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 32768/32768 K ( ...