Java设计模式 - 单例模式(创建型模式)
单例模式我在上学期看一些资料时候学习过,没想到这学期的软件体系结构就有设计模式学习,不过看似篇幅不大,介绍得比较简单,在这里我总结下单例模式,一来整理之前的笔记,二来也算是预习复习课程了。
概述
单例模式 (Singleton Pattern) 是 Java 中最简单的设计模式之一,属于一种创建型模式。
单例模式保证对于每一个类加载器,一个类仅有一个唯一的实例对象,并提供一个全局的唯一访问点。
优缺点
优点:
- 减少内存开销。
- 避免对资源的多重占用。
- 严格控制客户程序访问其唯一的实例。
- 单例类的子类都是单例类。
- 比较容易改写为允许一定数目对象的类。
缺点:
- 不适用于变化的对象。
- 由于没有抽象层,难以扩展。
- 职责过重,一定程度上违背了“单一职责原则”。
核心思路
- 构造器私有化。
- 私有的静态常量成员。
- 公开的静态方法
getInstance(),返回静态实例对象。 - 确保每次访问的实例对象都是同一个对象。
- 多线程中要保证线程安全。
实现方式
实现单例方式有五种:饿汉式、懒汉式、双重校验锁(DCL)、静态内部类、枚举。
饿汉式
优点:在类加载的时候就完成实例化,避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
public class Singleton1 {
private static final Singleton1 INSTANCE = new Singleton1();
private Singleton1() {
System.out.println("饿汉式,可用");
}
public static Singleton1 getInstance() {
return INSTANCE;
}
}
懒汉式
优点:达到了懒加载的效果。
缺点:线程不安全。
对于普通懒汉式,线程不安全。
原因:在进入
if (instance == null)时,可能一个线程进入后就切换到了另一个线程,而此时并未创建实例对象,这个线程又再次进入了if代码块。public class Singleton2 {
private static Singleton2 instance; private Singleton2() {
System.out.println("懒汉式,线程不安全,多线程不可用");
} public static Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
}
对方法添加
synchronized保证线程安全。synchronized保证了getInstance()线程安全,但对方法进行同步效率不高。public class Singleton3 {
private static Singleton3 instance; private Singleton3() {
System.out.println("懒汉式(synchronized方法),效率太低");
} public static synchronized Singleton3 getInstance() {
if (instance == null) {
instance = new Singleton3();
}
return instance;
}
}
改进方案2,使用
synchronized代码块。使用了
synchronized代码块改进,但又出现了和方案1一样的线程安全问题。public class Singleton4 {
private static Singleton4 instance; private Singleton4() {
System.out.println("懒汉式(synchronized代码块),线程不安全,多线程不可用");
} public static Singleton4 getInstance() {
if (instance == null) {
synchronized (Singleton4.class) {
// 此处若有线程阻塞,其它线程就仍可以进入到了前面if
instance = new Singleton4();
}
}
return instance;
}
}
双重校验锁(DCL)
针对上一个改进,使用两个if (instance == null) 。
这里先别管实现的Serializable接口和readResolve()方法,这两个用于后面的序列化测试。
另外,此处的volatile声明是很重要的,volatile变量有两种特性。
一是保证了此变量对所有线程的可见性。“可见性”指的是当一条线程修改了这个变量的值,新值对于其它线程来说是可以立即知道的。
二是禁止指令重排序优化。但volatile变量的运行在并发编程下并非是安全的,因为不能保证原子性。详细请自己去看相关资料。
import java.io.Serializable;
public class Singleton5 implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Singleton5 instance;
private Singleton5() {
System.out.println("懒汉式优化(Double Check Lock)双重校验锁,推荐用");
}
public static Singleton5 getInstance() {
if (instance == null) {
// 未被初始化,但是无法确定这时其他线程是否已经对其初始化,因此添加对象锁进行互斥
synchronized (Singleton5.class) {
// 再一次进行检查,因为有可能在当前线程阻塞的时候,其他线程对instance进行初始化
if (instance == null) {
// 此时还未被初始化的话,在这里初始化可以保证线程安全
instance = new Singleton5();
}
}
}
return instance;
}
// 如果该对象被用于序列化,该方法可以保证对象在序列化前后保持一致
private Object readResolve() {
return getInstance();
}
}
静态内部类
静态内部类方式在类被加载时并不会立即实例化。
而是在需要实例化时,调用getInstance()方法,才会装载内部类,从而完成对象实例化。
import java.io.Serializable;
public class Singleton6 implements Serializable {
private static final long serialVersionUID = 1L;
private Singleton6() {
System.out.println("饿汉式优化(静态内部类),推荐用");
}
private static class SingletonInstance {
private static final Singleton6 INSTANCE = new Singleton6();
}
public static Singleton6 getInstance() {
return SingletonInstance.INSTANCE;
}
// 如果该对象被用于序列化,该方法可以保证对象在序列化前后保持一致
private Object readResolve() {
return getInstance();
}
}
枚举
枚举方法不仅保证了线程安全问题,还提供了序列化机制。
public enum Singleton7 {
INSTANCE;
private Singleton7() {
System.out.println("枚举,最好方法");
}
public static Singleton7 getInstance() {
return INSTANCE;
}
}
测试
线程安全测试
public class TestSafety {
static Singleton1 s1_a;
static Singleton1 s1_b;
static Singleton2 s2_a;
static Singleton2 s2_b;
static Singleton3 s3_a;
static Singleton3 s3_b;
static Singleton4 s4_a;
static Singleton4 s4_b;
static Singleton5 s5_a;
static Singleton5 s5_b;
static Singleton6 s6_a;
static Singleton6 s6_b;
static Singleton7 s7_a;
static Singleton7 s7_b;
public static void getS1_A() {
s1_a = Singleton1.getInstance();
}
public static void getS1_B() {
s1_b = Singleton1.getInstance();
}
public static void getS2_A() {
s2_a = Singleton2.getInstance();
}
public static void getS2_B() {
s2_b = Singleton2.getInstance();
}
public static void getS3_A() {
s3_a = Singleton3.getInstance();
}
public static void getS3_B() {
s3_b = Singleton3.getInstance();
}
public static void getS4_A() {
s4_a = Singleton4.getInstance();
}
public static void getS4_B() {
s4_b = Singleton4.getInstance();
}
public static void getS5_A() {
s5_a = Singleton5.getInstance();
}
public static void getS5_B() {
s5_b = Singleton5.getInstance();
}
public static void getS6_A() {
s6_a = Singleton6.getInstance();
}
public static void getS6_B() {
s6_b = Singleton6.getInstance();
}
public static void getS7_A() {
s7_a = Singleton7.getInstance();
}
public static void getS7_B() {
s7_b = Singleton7.getInstance();
}
public static void main(String[] args) {
Thread t1_a = new Thread(TestSafety::getS1_A);
Thread t1_b = new Thread(TestSafety::getS1_B);
t1_a.start();
t1_b.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s1_a == s1_b ? " + (s1_a == s1_b));
System.out.println();
Thread t2_a = new Thread(TestSafety::getS2_A);
Thread t2_b = new Thread(TestSafety::getS2_B);
t2_a.start();
t2_b.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s2_a == s2_b ? " + (s2_a == s2_b));
System.out.println();
Thread t3_a = new Thread(TestSafety::getS3_A);
Thread t3_b = new Thread(TestSafety::getS3_B);
t3_a.start();
t3_b.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s3_a == s3_b ? " + (s3_a == s3_b));
System.out.println();
Thread t4_a = new Thread(TestSafety::getS4_A);
Thread t4_b = new Thread(TestSafety::getS4_B);
t4_a.start();
t4_b.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s4_a == s4_b ? " + (s4_a == s4_b));
System.out.println();
Thread t5_a = new Thread(TestSafety::getS5_A);
Thread t5_b = new Thread(TestSafety::getS5_B);
t5_a.start();
t5_b.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s5_a == s5_b ? " + (s5_a == s5_b));
System.out.println();
Thread t6_a = new Thread(TestSafety::getS6_A);
Thread t6_b = new Thread(TestSafety::getS6_B);
t6_a.start();
t6_b.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s6_a == s6_b ? " + (s6_a == s6_b));
System.out.println();
Thread t7_a = new Thread(TestSafety::getS7_A);
Thread t7_b = new Thread(TestSafety::getS7_B);
t7_a.start();
t7_b.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("s7_a == s7_b ? " + (s7_a == s7_b));
System.out.println();
}
}
输出结果:
饿汉式,可用
s1_a == s1_b ? true
懒汉式,线程不安全,多线程不可用
懒汉式,线程不安全,多线程不可用
s2_a == s2_b ? false
懒汉式(synchronized方法),效率太低
s3_a == s3_b ? true
懒汉式(synchronized代码块),线程不安全,多线程不可用
懒汉式(synchronized代码块),线程不安全,多线程不可用
s4_a == s4_b ? false
懒汉式优化(Double Check Lock)双重校验锁,推荐用
s5_a == s5_b ? true
饿汉式优化(静态内部类),推荐用
s6_a == s6_b ? true
枚举,最好方法
s7_a == s7_b ? true
序列化测试
只对双重校验锁、静态内部类、枚举测试。
默认只有枚举能保证序列化后对象仍相等,其它需要加readResolve()方法才能。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
public class TestSerializable {
// 测试序列化后的对象是否相等
public static void main(String[] args) {
Singleton5 s5_a = Singleton5.getInstance();
Singleton5 s5_b = Singleton5.getInstance();
System.out.println("序列化前:s5_a == s5_b ? " + (s5_a == s5_b));
Path file5 = Paths.get("object5.txt");
try (ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(file5.toFile()))) {
out.writeObject(s5_a);
} catch (Exception e) {
e.printStackTrace();
}
try (ObjectInputStream in = new ObjectInputStream(
new FileInputStream(file5.toFile()))) {
s5_b = (Singleton5) in.readObject();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("序列化后:s5_a == s5_b ? " + (s5_a == s5_b));
System.out.println();
Singleton6 s6_a = Singleton6.getInstance();
Singleton6 s6_b = Singleton6.getInstance();
System.out.println("序列化前:s6_a == s6_b ? " + (s6_a == s6_b));
Path file6 = Paths.get("object6.txt");
try (ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(file6.toFile()))) {
out.writeObject(s6_a);
} catch (Exception e) {
e.printStackTrace();
}
try (ObjectInputStream in = new ObjectInputStream(
new FileInputStream(file6.toFile()))) {
s6_b = (Singleton6) in.readObject();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("序列化后:s6_a == s6_b ? " + (s6_a == s6_b));
System.out.println();
Singleton7 s7_a = Singleton7.getInstance();
Singleton7 s7_b = Singleton7.getInstance();
System.out.println("序列化前:s7_a == s7_b ? " + (s7_a == s7_b));
Path file7 = Paths.get("object7.txt");
try (ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(file7.toFile()))) {
out.writeObject(s7_a);
} catch (Exception e) {
e.printStackTrace();
}
try (ObjectInputStream in = new ObjectInputStream(
new FileInputStream(file7.toFile()))) {
s7_b = (Singleton7) in.readObject();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("序列化后:s7_a == s7_b ? " + (s7_a == s7_b));
System.out.println();
}
}
先把readResolve()方法注释掉;输出结果为:
懒汉式优化(Double Check Lock)双重校验锁,推荐用
序列化前:s5_a == s5_b ? true
序列化后:s5_a == s5_b ? false
饿汉式优化(静态内部类),推荐用
序列化前:s6_a == s6_b ? true
序列化后:s6_a == s6_b ? false
枚举,最好方法
序列化前:s7_a == s7_b ? true
序列化后:s7_a == s7_b ? true
加上readResolve()方法,枚举不需要,枚举甚至连Serializable接口都不需实现;输出结果如下:
懒汉式优化(Double Check Lock)双重校验锁,推荐用
序列化前:s5_a == s5_b ? true
序列化后:s5_a == s5_b ? true
饿汉式优化(静态内部类),推荐用
序列化前:s6_a == s6_b ? true
序列化后:s6_a == s6_b ? true
枚举,最好方法
序列化前:s7_a == s7_b ? true
序列化后:s7_a == s7_b ? true
使用场景
- 文件管理系统
- 日志记录类
- 与数据库的连接
应用实例
java.lang.Runtime#getRuntime()
Java设计模式 - 单例模式(创建型模式)的更多相关文章
- Java设计模式之创建型模式
创建型模式分为五类:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式 一.工厂方法模式:接口-实现类.工厂类
- Java设计模式_创建型模式_单例模式
单例模式的实现: 定义一个类,在类中定义该类的静态变量,再定一个一个获取该类的静态变量的方法. UML图:
- [C#]设计模式-单例模式-创建型模式
单例模式用于在整个软件系统当中保持唯一实例,在 C# 当中最能够体现此概念的就是静态类,静态类的生命周期是跟随整个程序,并且在整个程序中仅保有一个实例. 不过在这里我们不再详细阐述单例模式与静态类有什 ...
- Java设计模式 - - 单例模式 装饰者模式
Java设计模式 单例模式 装饰者模式 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 静态代理模式:https://www.cnblogs.com/StanleyBlogs/p/1 ...
- 设计模式(Java版)-创建型模式之简单工厂模式
前言:这段时间在学习设计模式,本人也是小菜一枚(所以写的如果有错误的地方请大大们给予指出).这个东西也是我一直想学习的,从点点滴滴做起,记录下自己每天的领悟! 一.工厂模式的动机 在软件系统中,经常面 ...
- GoF的23种设计模式之创建型模式的特点和分类
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”.这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成.就像我们去商场购买商品时, ...
- Typescript玩转设计模式 之 创建型模式
作者简介 joey 蚂蚁金服·数据体验技术团队 前言 我们团队的工作是用单页面应用的方式实现web工具.涉及到数万到十数万行的前端代码的管理,而且项目周期长达数年. 怎么样很好地管理好这种量级的前端代 ...
- 单例模式——创建型模式01
1. 名称 单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类.单例模式是一种对象创建型模式. 2. 问题 ...
- 设计模式01 创建型模式 - 单例模式(Singleton Pattern)
参考 [1] 设计模式之:创建型设计模式(6种) | 博客园 [2] 单例模式的八种写法比较 | 博客园 单例模式(Singleton Pattern) 确保一个类有且仅有一个实例,并且为客户提供一 ...
- Java设计模式之职责型模式总结
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6548127.html 所谓职责型模式,就是采用各种模式来分配各个类的职责. 职责型模式包括 ...
随机推荐
- python 之os模块用法大全
Python的标准库中的os模块包含普遍的操作系统功能.这个模块的作用主要是提供与平台无关的功能.也就是说os模块能够处理平台间的差异问题,使得编写好的程序无需做任何改动就能在另外的平台上运行 这边给 ...
- win7 部署tomcat
1,下载 jdk:http://www.oracle.com/technetwork/java/javase/downloads/jdk-7u3-download-1501626.html 2,下载t ...
- Vmware启动ubuntu 出现错误。
Vmware启动ubuntu 出现错误“以独占方式锁定此配置文件失败. 可能其它正在运行VMware进程在使用此配置文件”. 在网上查找了很多方法,法(1)试过在启动任务管理器中“结束与VMware有 ...
- 那些让你觉得自己是个傻B的题目集锦(大神的降维打击合集)
一起过来排好队,进来挨打 1.Leetcode tag-LinkList 109.convert sorted list to binary search tree 2Leetcode tag-Arr ...
- LuoGu-P2863牛的舞会The Cow Prom[tarjan 缩点模板]
传送门:https://www.luogu.org/problemnew/show/P2863 思路:tarjan模板题,之前会的tarjan,一直想学缩点到底是什么操作,发现就是把同组的放在一个数组 ...
- CodeForces 988 F Rain and Umbrellas
Rain and Umbrellas 题意:某同学从x=0的点走到x=a的点,路上有几段路程是下雨的, 如果他需要经过这几段下雨的路程, 需要手上有伞, 每一把伞有一个重量, 求走到重点重量×路程的最 ...
- whu-contest-2019(online)
比赛网址:http://whu2019.contest.codeforces.com/group/YyBKO8xFiH/contest/102167 赛后总结: T:今天参加了武汉大学校赛网络赛,在c ...
- 牛客小白月赛4 B 博弈论 思维 字符串
链接:https://www.nowcoder.com/acm/contest/134/B来源:牛客网 题目描述 铁子和顺溜在学习了博弈论的sg函数之后,解决了很多很多博弈题,现在他们遇到了一道难题. ...
- 天梯杯 L2-005. 集合相似度
L2-005. 集合相似度 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 给定两个整数集合,它们的相似度定义为:Nc/Nt*1 ...
- Hexo博客Next v7.X 主题升级,美化警示录
本文转载于:Hexo博客Next v7.X 主题升级,美化警示录丨奥怪的小栈 前言 经历了好几天(懒癌晚期懒得数了)的与主题升级斗争后,我终于完成基本上完成了next主题的升级!升到了V7.3!哈哈哈 ...