开始学习Java的设计模式,因为做了很多年C语言,所以语言基础的学习很快,但是面向过程向面向对象的编程思想的转变还是需要耗费很多的代码量的。所有希望通过设计模式的学习,能更深入的学习。

  把学习过程中的笔记,记录下来,只记干货。

第一部分:单例模式的内容

  单例模式:类只能有一个实例。

  类的特点:1、私有构造器;2、内部构造实例对象;3、对外提供获取唯一实例的public方法。

  常见的单例模式实现有五种形式:

    1、饿汉式。

    2、懒汉式。

    3、双重检查锁式。

    4、静态内部类式。

    5、枚举式。

  以下分别介绍:

  一、饿汉式

    饿汉式单例特点:线程安全,不能延时加载,效率较高。

 public class SingletonDemoE {

     //内部构建唯一实例
private static SingletonDemoE instance = new SingletonDemoE(); //私有化构造器
private SingletonDemoE(){ } //公共静态方法获取唯一实例化对象
public static SingletonDemoE getInstance(){
return instance;
} }

  二、懒汉式

    懒汉式单例特点:线程安全(须synchronized做方法同步),可以延时加载,效率较低。

 public class SingletonDemoL {

     //声明实例对象
private static SingletonDemoL instance; //私有化构造器
private SingletonDemoL(){ } //公共静态方法获取唯一实例化对象,方法同步
public static synchronized SingletonDemoL getInstance(){
if(instance == null){
//第一次实例化时构建
instance = new SingletonDemoL();
}
return instance;
} }

  三、双重检查锁式

    结合了饿汉式和懒汉式的优点,但由于JVM底层内部模型原因,偶尔会出问题,所以不建议使用,本文不赘语。

  四、静态内部类式

    静态内部类式单例特点:线程安全(须synchronized做方法同步),可以延时加载,效率较高。

 public class SingletonDemoJ {

     //静态内部类
private static class SingletonClassInstance {
private static final SingletonDemoJ instance = new SingletonDemoJ();
} //私有化构造器
private SingletonDemoJ(){ } //公共静态方法获取唯一实例化对象,方法同步
public static synchronized SingletonDemoJ getInstance(){
return SingletonClassInstance.instance;
} }

  五、枚举式

    枚举式单例特点:枚举是天然的单例,线程安全,不可延时加载,效率较高

 public enum SingletonDemoM {
//枚举元素,本身就是单例模式
INSTANCE; //实现自己的操作
public void singletonOperation(){ }
}

第二部分:单例模式的破解(扩展)

  单例模式的五种实现方式中,除枚举式是天然的单例不可破解之外,其他四种形式均可通过反射和反序列化的机制进行破解。

  以懒汉式单例为例,首先分别看一下如何通过反射和反序列化的机制破解单例。

  定义一个常规的懒汉式单例:

 public class SingletonDemoAntiCrackL {
private static SingletonDemoAntiCrackL instance; private SingletonDemoAntiCrackL(){ } public static synchronized SingletonDemoAntiCrackL getInstance(){
if(instance == null){
instance = new SingletonDemoAntiCrackL();
}
return instance;
}
}

  正常我们创建多个单例的实例,都应该是同一个对象,如下测试Demo:

 public class TestCrackDemo {
public static void main(String[] args) {
SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance();
SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance();
System.out.println("sL1 = " + sL1);
System.out.println("sL2 = " + sL2);
}
}

  运行返回:

sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55

  利用反射机制破解单例,创建多个不同的实例:

 package com.corey.singleton;

 import java.lang.reflect.Constructor;

 public class TestCrackDemo {
public static void main(String[] args) {
SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance();
SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance();
System.out.println("sL1 = " + sL1);
System.out.println("sL2 = " + sL2); //利用反射机制破解单例
try {
Class<SingletonDemoAntiCrackL> clazz = (Class<SingletonDemoAntiCrackL>)Class.forName("com.corey.singleton.SingletonDemoAntiCrackL");
Constructor<SingletonDemoAntiCrackL> c = clazz.getDeclaredConstructor(null);
c.setAccessible(true); //跳过权限检查,可以访问私有属性和方法
SingletonDemoAntiCrackL sL3 = c.newInstance(null); //构建实例
SingletonDemoAntiCrackL sL4 = c.newInstance(null);
System.out.println("sL3 = " + sL3);
System.out.println("sL4 = " + sL4); } catch (Exception e) {
e.printStackTrace();
}
}
}

  运行返回:

sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL3 = com.corey.singleton.SingletonDemoAntiCrackL@15db9742
sL4 = com.corey.singleton.SingletonDemoAntiCrackL@6d06d69c

  可见,通过反射构建的sL3和sL4两个对象,都是不同的实例,破解了单例模式只能有一个实例的要求。

  那么,修改单例的构造函数,可以应对反射机制的破解,代码如下:

 public class SingletonDemoAntiCrackL {
private static SingletonDemoAntiCrackL instance; private SingletonDemoAntiCrackL(){
//私有构造器,增加实例检查,若已创建实例,则抛出异常
6 if(instance != null){
7 throw new RuntimeException();
8 }
} public static synchronized SingletonDemoAntiCrackL getInstance(){
if(instance == null){
instance = new SingletonDemoAntiCrackL();
}
return instance;
}
}

  此时,在运行TestCrackDemo时,会抛出java.lang.reflect.InvocationTargetException异常,避免了通过反射机制创建多个实例的问题。

  接下来,看下通过反序列化机制破解单例。

  当单例的类实现了Serializable接口时,就可以通过反序列化机制破解,如下单例:

 public class SingletonDemoAntiCrackL implements Serializable{
private static SingletonDemoAntiCrackL instance; private SingletonDemoAntiCrackL(){
//私有构造器,增加实例检查,若已创建实例,则抛出异常
if(instance != null){
throw new RuntimeException();
}
} public static synchronized SingletonDemoAntiCrackL getInstance(){
if(instance == null){
instance = new SingletonDemoAntiCrackL();
}
return instance;
}
}

  反序列化破解测试Demo:

 package com.corey.singleton;

 import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor; public class TestCrackDemo {
public static void main(String[] args) {
SingletonDemoAntiCrackL sL1 = SingletonDemoAntiCrackL.getInstance();
SingletonDemoAntiCrackL sL2 = SingletonDemoAntiCrackL.getInstance();
System.out.println("sL1 = " + sL1);
System.out.println("sL2 = " + sL2); //通过反序列化机制破解单例
try {
//序列化,将对象存入文件
FileOutputStream fos = new FileOutputStream("f:/fos.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(sL1);
oos.close();
fos.close();
//反序列化,从文件中读出对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("f:/fos.txt"));
SingletonDemoAntiCrackL sL5 = (SingletonDemoAntiCrackL)ois.readObject();
System.out.println("sL5 = " + sL5);
} catch (Exception e) {
e.printStackTrace();
}
}
}

  运行的结果:

sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL5 = com.corey.singleton.SingletonDemoAntiCrackL@33909752

  可见,反序列化出来的实例对象,是不同的对象,即单例已被破解。

  解决版本,单例类中重写readResolve方法,可以应对反射机制的破解,代码如下:

 package com.corey.singleton;

 import java.io.ObjectStreamException;
import java.io.Serializable; /**
* 单例模式,懒汉式
* 线程安全,能延时加载,效率相对较低
* @author Corey
*
*/
public class SingletonDemoAntiCrackL implements Serializable{
private static SingletonDemoAntiCrackL instance; private SingletonDemoAntiCrackL(){
//私有构造器,增加实例检查,若已创建实例,则抛出异常
if(instance != null){
throw new RuntimeException();
}
} public static synchronized SingletonDemoAntiCrackL getInstance(){
if(instance == null){
instance = new SingletonDemoAntiCrackL();
}
return instance;
} private Object readResolve() throws ObjectStreamException{
//反序列化时,直接返回对象
return instance;
}
}

  修改后,再次运行TestCrackDemo,可以看到反序列化后,构建的仍然是同一个对象。

sL1 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL2 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55
sL5 = com.corey.singleton.SingletonDemoAntiCrackL@2a139a55

第三部分:单例模式各个实现方式的效率

  采用如下代码,测试:

 package com.corey.singleton;

 import java.util.concurrent.CountDownLatch;

 /**
* 测试单例模式的效率
* @author Corey
*
*/
public class TestEfficiencyDemo {
public static void main(String[] args) throws Exception { int threadNum = 10;
long start = System.currentTimeMillis(); final CountDownLatch cdl = new CountDownLatch(threadNum); //创建10个线程
for(int k=0; k<threadNum; k++){
new Thread(new Runnable() { @Override
public void run() {
//每个线程构建100万个实例对象
for(int i=0; i<1000000; i++){
Object o = SingletonDemoE.getInstance();
}
//每个线程运行完毕,线程计数器减一
cdl.countDown();
}
}).start();
} cdl.await();//main线程阻塞,直到现场计数器为0,才继续执行。 long end = System.currentTimeMillis();
System.out.println("饿汉式总耗时:" + (end - start));
}
}

  运行结果:

饿汉式总耗时:17

  以此类推,测试各个实现方式的单例的效率。注意,此处根据电脑性能以及电脑的运行情况不同,结果都是不一样的,甚至同一实现方式,多次运行的结果也不一样。

  我这里的测试结果如下:

饿汉式总耗时:17
懒汉式总耗时:171
静态内部类式总耗时:165
枚举式总耗时:11

  以上就是设计模式中的单例模式!

Java设计模式学习笔记,一:单例模式的更多相关文章

  1. Java设计模式学习笔记(五) 单例模式

    前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 1. 使用单例模式的原因 以Windows任务管理器为例,在Windows系统中,任务管理器是唯 ...

  2. Java设计模式学习笔记(单例模式)

    最近一直在看<Head First设计模式>,这本书写的确实是很不错的,专注于怎么用最简单的方式最通俗的语言让人了解设计模式.据说GoF的设计模式那本书写的很好,是一本经典,但是就是难懂, ...

  3. java设计模式学习笔记--接口隔离原则

    接口隔离原则简述 客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应建立在最小的接口上 应用场景 如下UML图 类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类 ...

  4. java设计模式学习笔记--单一职责原则

    单一职责原则注意事项和细节 1.降低类的复杂度,一个类只负责一项职责 2.提高可读性,可维护性 3.降低变更引起的风险 4.通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单 ...

  5. java设计模式学习笔记--浅谈设计模式

    设计模式的目的 编写软件的过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战.设计模式为了让程序具有更好的 1.代码重用性(即:相同功能的代码,不用多次编写) ...

  6. Java设计模式学习笔记(二) 简单工厂模式

    前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 正文开始... 1. 简介 简单工厂模式不属于GoF23中设计模式之一,但在软件开发中应用也较为 ...

  7. Java设计模式学习笔记(三) 工厂方法模式

    前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 1. 简介 上一篇博客介绍了简单工厂模式,简单工厂模式存在一个很严重的问题: 就是当系统需要引入 ...

  8. Java设计模式学习笔记(四) 抽象工厂模式

    前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 1. 抽象工厂模式概述 工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问 ...

  9. Java设计模式学习笔记(一) 设计模式概述

    前言 大约在一年前学习过一段时间的设计模式,但是当时自己的学习方式比较低效,也没有深刻的去理解.运用所学的知识. 所以现在准备系统的再重新学习一遍,写一个关于设计模式的系列博客. 废话不多说,正文开始 ...

随机推荐

  1. HTTPS反向代理嗅探

    两年前的文章,一直在本地笔记放着,发现博客上竟然没存.发出来. 先说说什么是SSL:     SSL是Secure Socket Layer的简称,中文意思是安全套接层,由NetScape公司开发,用 ...

  2. 笔记,spring4+ehcache2配置文件

    最近工作中遇到个功能需要整合ehcache,由于spring版本用的是最新的4.2.4,而在ehcache官网找到的集成配置文档是spring3.1的,因此配了几次都不成功,在历经一番波折后终于成功集 ...

  3. 多重bash登入的history写入问题

    问题:如果一个用户同时开好几个 bash 接口, 这时~/.bash_history中会写入哪个bash的历史命令记录? 答:所有的bash 都有自己的 HISTSIZE 笔记录在内存中,因为等到注销 ...

  4. 连接SQL SERVER数据库实例出错

    问题背景: 刚买的新电脑,昨天让公司IT部装了win10专业版系统,想体验一下.装完后一切正常,晚上带回家装自己需要的软件和系统更新,结果今天来公司发现数据库实例连不上了,拿到IT部,他们也没有找到原 ...

  5. ecshop 商品分类页 取得当前分类下的子分类方法

    ecshop的商品分类页面category.php 下的分类,默认是取得所有同级父分类以及父类别的子分类.比如,我点击进入是A商品分类的页面 category.php?id=1,事实上 我只需要取得父 ...

  6. 关于对WEB标准以及W3C的理解与认识问题

    web标准简单来说可以分为结构.表现和行为.其中结构主要是有HTML标签组成.或许通俗点说,在页面body里面我们写入的标签都是为了页面的结构.表现即指css样式表,通过css可以是页面的结构标签更具 ...

  7. 【LCA求最近公共祖先+vector构图】Distance Queries

    Distance Queries 时间限制: 1 Sec  内存限制: 128 MB 题目描述 约翰的奶牛们拒绝跑他的马拉松,因为她们悠闲的生活不能承受他选择的长长的赛道.因此他决心找一条更合理的赛道 ...

  8. excel转html 实现在线预览

    首先说一下,本人发布的代码都是经过本人亲测,并且用在实际项目中.如果觉得可以,希望大家点个赞,谢谢大家. 有什么问题,大家评论出来,一起交流.好了,不废话了,下面来说一说这个东西怎么做. 网上也有许多 ...

  9. 《HelloGitHub》第 15 期

    公告 这段时间没怎么写文章,跑去写 https://hellogithub.com 这个网站了,现在已经顺利上线,功能后面会持续迭代. 最后,这个 https://hellogithub.com 网站 ...

  10. java 获得当前时间 年月日时分秒 星期几

    <%SimpleDateFormat df = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");//设置日期格式SimpleDat ...