当前位置:网站首页>常见的单例模式&简单工厂
常见的单例模式&简单工厂
2022-06-24 13:02:00 【一麟yl】
目录
前言:
为什么我们要学习设计模式:设计模式(disign pattern)代表了最佳的实践,是很多优秀的软件开发人员的经验所得,是为了解决特定的问题制作的方案。它并不是语法规定,也不会拘泥特定语言。恰当的使用设计模式可以让代码更有可复用性、可维护性、可扩展性、健壮性以及安全性,这些都是系统非常重要的非功能性需求。
首先我们来简单分析一下常见的几种单例模式。
单例模式:
1.单例模式的概念:其概念顾名思义就是只会在内存中用到一个实例。
2.两种单例模式的区别:
①两者其实最主要的区别就在于创建对象的时机不同:饿汉模式是在类加载的时候就会创建对象,而懒汉模式则是在使用的时候创建(也就是获取实例对象方法被调用的时候创建)。
②其次就是两者的线程安全问题:饿汉模式因为是单线程模式,所以不会出现线程安全问题。而懒汉模式就会出现这种问题。
3.单例模式的优缺点:
优点:
- 在内存中只有一个对象,节省内存空间;
- 避免频繁的创建销毁对象,可以提高性能;
- 避免对共享资源的多重占用,简化访问;
- 为整个系统提供一个全局访问点。
缺点:
- 不适用于变化频繁的对象;
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
单例模式又被分为以下两种:
一,饿汉模式
简单来将:饿汉模式就是在定义单例对象的同时将其实例化的,直接使用便可。也就是说在饿汉模式下,在SingletonDemo01完成该类的实例便已经存在于JVM中了。这样来讲是不是就更下容易理解了。
代码如下:
package com.ljq.text;
/**
* 单例模式-饿汉模式
* @author 一麟
*
*/
public class SingletonDemo01 {
// 1. 需要有一个私有的构造函数,防止该类通过new的方式创建实例
private SingletonDemo01() {
}
// 2. 饥饿模式,首先生成一个实例
private static final SingletonDemo01 instance = new SingletonDemo01();
// 3. 静态方法,用于获取已经生成的实例
public static SingletonDemo01 getInstance() {
return instance;
}
public String hello(String name) {
return "hello " + name;
}
/**
* 测试多线程是否安全方法
*
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(SingletonDemo01.getInstance().hashCode());
}).start();
}
SingletonDemo01 s = SingletonDemo01.getInstance();
String hello_world = s.hello("world");
System.out.println(hello_world);
}
}
实现原理:
1️⃣将构造方法私有化,以防止该类不会被以new的形式实例出来
2️⃣创建一个静态变量,这个常量就是new出来的该类,之所以为静态是因为静态属性或方法是属于类的,能够更好的保证单例模式的唯一性。
3️⃣创建静态方法用于返还常量,也就是已经生成的实例
这里我们顺便也创建一个普通方法hello传入一个string参数用于返还hello于该参数的拼接字符串以方便我们测试使用。
测试:
1️⃣这里调用100个线程通过类名调用静态方法再使用hashcode方法打印出最终的结果。
2️⃣然后测试通过类名调用静态方法getInstance获得该类的实例类再调用hello的打印方法。
控制台结果如下:

我们就会发现,使用hashcode方法打印出来的结果都是一样的。
二,懒汉模式
懒汉模式是有存在浪费资源的可能。
饿汉模式又主要有四种方式:
1.单向判断简易法
这个其实算是比较简单的懒汉模式,所以后面的方法也是在这个上面进行改动的。
代码如下:
package com.ljq.text;
import java.net.Socket;
/**
* 单例模式-懒汉式(存在线程安全问题)
*
* @author 一麟
*
*/
public class SingletonDemo02 {
private SingletonDemo02() {
// 模拟构造函数的运行耗时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static SingletonDemo02 singletonDemo02 = null;
public static synchronized SingletonDemo02 getInstance() {
if (singletonDemo02 == null) {
singletonDemo02 = new SingletonDemo02();
}
return singletonDemo02;
}
public String hello(String name) {
return "hello " + name;
}
/*
* 测试多线程下单例模式是否安全
*
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(SingletonDemo02.getInstance().hashCode());
}).start();
}
}
}
实现原理:
1️⃣将构造方法私有化,并在构造方法中模拟构造方法的运行耗时 。
2️⃣定义静态变量并将其初始化为null
3️⃣定义获得实例类的方法并加上同步锁,在方法体中加上判断,如果静态常量为null值的话就给其赋值
我们测试发现:
通过控制台结果我们遍历的一百个线程结果都是一致的,但是其实不是这样的,这个方式任然存在安全性问题,只是不是每回都会出现。
2.双重校验锁法
比较我们上面的单向判断的方法,懒汉模式虽然能够实现多线程下的单例,但是粗暴的将getInstance()锁住了,这样的代价其实很大的,为什么?
因为,只有当第一次调用getInstance()是才需要同步创建对象的,创建之后再次调用getInstance()是就只是简单的返回成员变量,而这里是无需同步的,所以没必要对整个方法加锁。
由于同步一个方法会降低100倍的或者更高的性能,每次调用获取或者释放锁的开销是可以避免的:一旦初始化完成,获得释放锁就显得很不必要了。
所以就有了双重校验锁模式:
双锁模式指在懒汉模式的基础上做进一步优化,给静态对象的定义加上volatile锁来保障初始化时对象的唯一性,在获取对象时通过synchronized (Singleton.class)给单例类加锁来保障操作的唯一性。
代码如下:
package com.ljq.text;
/**
* 单例模式-懒汉式(双重判断式,线程安全,但性能较低)
*
* @author 一麟
*
*/
public class SingletonDemo03 {
private SingletonDemo03() {
// 模拟构造函数的运行耗时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static SingletonDemo03 singletonDemo03 = null;
public static SingletonDemo03 getInstance() {
// 系统减小同步块来提升性能,可以吗?
if (singletonDemo03 == null) {
/// ....
synchronized (SingletonDemo03.class) {
if (singletonDemo03 == null) {
singletonDemo03 = new SingletonDemo03();
}
}
}
return singletonDemo03;
}
public String hello(String name) {
return "hello " + name;
}
/*
* 测试多线程下单例模式是否安全
*
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(SingletonDemo03.getInstance().hashCode());
}).start();
}
}
}
其实整个过程也很清晰简单的:
①检查变量是否被初始化(先不去获得锁),如果已经被初始化就立即返回这个变量
②获得锁
③第二次检查变量是否已经被初始化:如果其他线程曾获取过锁,那么变量就已经被初始化了,返回初始化的变量
④否则,初始化并返回变量
所以啊,之所以要第二次校验就是为了防止再次创建,假如有一种情况,当singletonDemo03 还未被创建时线程T1调用getInstance(),由于第一次判断singletonDemo03 ==null,此时线程T1准备继续执行,但是由于资源被线程T2抢占了,此时T2调用getInstance(),同样地,由于singletonDemo03 并没有实例化,T2同样可以通过第一个if,然后继续往下执行,同步代码块,第二个if也通过,然后线程T2创建了一个实例singletonDemo03 。此时线程T2完成任务,资源又回到线程T1,T1此时也进入同步代码块,如果没有这个第二个if,那么,T1也会创建一个singletonDemo03 实例,那么,就会出现创建多个实例的情况,但是加上第二个if,就可以完全避免这个多线程导致多次创建实例的问题。
3.通过静态内部类实例化该类
代码如下:
package com.ljq.text;
/**
* 单例模式-懒加载(线程安全)
*
* @author 一麟
*
*/
public class SingletonDemo04 {
// 阻止外部实例化
private SingletonDemo04() {
// 模拟构造函数运行耗时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static {
}
// 使用静态内部类来使用一个SingletonDemo04对象
private static class SingletonDemoHolder {
private final static SingletonDemo04 instance = new SingletonDemo04();
}
public static SingletonDemo04 getInstance() {
return SingletonDemoHolder.instance;
}
public String hello(String name) {
return "hello " + name;
}
/*
* 测试多线程下单例模式是否安全
*
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(SingletonDemo04.getInstance().hashCode());
}).start();
}
System.out.println(SingletonDemo04.getInstance());
}
}
测试结果:
4.枚举类法:
Enum的全称为enumeration,中文俗称枚举类。
说到枚举类,学过或者有了解过C/C++的人应该都对他略知一二。
但是在Java语言范畴中,是在JDK5的版本中才引入的。enum就是用来声明的关键字
代码如下:
package com.ljq.text;
/**
* 单例模式-枚举类式
* @author 一麟
*
*/
public enum SingletonDemo05 {
INSTANCE;
public String hello(String name) {
return "hello " + name;
}
/*
* 测试多线程下单例模式是否安全
*
* @param args
*/
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(SingletonDemo05.INSTANCE.hashCode());
}).start();
}
}
}
工厂模式
1.工厂模式的概念:
用于产生对象的方法或者式类,称之为工厂。 上面所讲到的单例模式也可以看作为一个特殊的工厂。
2.工厂模式的使用场景:
为什么需要工作模式,原来使用new的方式感觉也很简单,且好懂?
使用工厂的原因是我们可以通过工厂模式,来集中控制对象的创建过程,这样可以给设计带来更多的灵活性。
比如:spring的IOC容器就是工厂模式的经典实现。
3.工厂方法:
用于生产指定系列的对象。已鸭子为例,鸭子有真的鸭子,橡皮鸭,电子玩具鸭等。如何能方便的创建出各种鸭子,并将创建过程控制起来,以便于以后的维护和扩展?
图解如下:

我们现在开了一个卖鸭子的店:
package com.ljq.factory;
/**
* 鸭子的抽象类
*
* @author 一麟
*
*/
public abstract class Duck {
abstract public void quack();
}
然后我们的店里面呢主要有四种鸭子:
package com.ljq.factory;
/**
*
* @author 一麟
*
*/
public class PinkDuck extends Duck {
@Override
public void quack() {
System.out.println("我是粉红鸭");
}
}
package com.ljq.factory;
/**
*
* @author 一麟
*
*/
public class WildDuck extends Duck {
@Override
public void quack() {
System.out.println("我是真鸭子");
}
}
package com.ljq.factory;
/**
*
* @author 一麟
*
*/
public class RubberDuck extends Duck {
@Override
public void quack() {
System.out.println("我是橡皮鸭,");
}
}
package com.ljq.factory;
/**
*
* @author 一麟
*
*/
public class DonaldDuck extends Duck {
@Override
public void quack() {
System.out.println("我是唐老鸭");
}
}
然后我们再需要一个服务员用于与客户进行沟通买哪个?:
package com.ljq.factory;
/**
*
* @author 一麟
*
*/
public class DuckFactory {
private DuckFactory() {
}
private static DuckFactory duckFactory = new DuckFactory();
public static final int WILD_DUCK = 1;
public static final int RUBBER_DUCK = 2;
public static final int DONALD_DUCK = 3;
public static final int Pink_DUCK = 4;
public static Duck getInstance(int duckType) {
switch (duckType) {
case WILD_DUCK:
return new WildDuck();
case RUBBER_DUCK:
return new RubberDuck();
case DONALD_DUCK:
return new DonaldDuck();
case Pink_DUCK:
return new PinkDuck();
default:
return null;
}
}
}
最后客人来了,客人对我们的服务非常满意,一口气买下了我们店的所有品种:
package com.ljq.factory;
/**
*
* @author 一麟
*
*/
public class Main {
public static void main(String[] args) {
Duck donaldDuck = DuckFactory.getInstance(DuckFactory.DONALD_DUCK);
donaldDuck.quack();
Duck wildDuck = DuckFactory.getInstance(DuckFactory.WILD_DUCK);
wildDuck.quack();
Duck pinkDuck = DuckFactory.getInstance(DuckFactory.Pink_DUCK);
pinkDuck.quack();
Duck rubberDuck = DuckFactory.getInstance(DuckFactory.RUBBER_DUCK);
rubberDuck.quack();
}
}
控制台打印出来的结果:

以上我们的简易工厂模式。这样来讲是不是更加通俗易懂了。
下期我会给大家带来抽象工厂以及更多的知识,希望我的理解对大家有所帮助。我们下期再见!
边栏推荐
- 【从零开始学zabbix】一丶Zabbix的介绍与部署Zabbix
- conda和pip命令
- A review of text contrastive learning
- Zhiyuan community weekly 86: Gary Marcus talks about three linguistic factors that can be used for reference in large model research; Google puts forward the Wensheng graph model parti which is compar
- 21set classic case
- Antd checkbox, limit the selected quantity
- 【R语言数据科学】(十四):随机变量和基本统计量
- 钛星数安加入龙蜥社区,共同打造网络安全生态
- 杰理之可能出现有些芯片音乐播放速度快【篇】
- js去除字符串空格
猜你喜欢

从谭浩强《C程序设计》上摘录的ASCII码表(常用字符与ASCII代码对照表)

Harmony os. (2)

SaaS management system solution of smart Park: enabling the park to realize information and digital management

Rasa 3.x 学习系列-非常荣幸成为 Rasa contributors 源码贡献者,和全世界的Rasa源码贡献者共建共享Rasa社区!
![[deep learning] storage form of nchw, nhwc and chwn format data](/img/4f/4478d96132eb2547f6ec09ae49639e.jpg)
[deep learning] storage form of nchw, nhwc and chwn format data

OpenHarmony 1
![Jerry's infrared filtering [chapter]](/img/6b/7c4b52d39a4c90f969674a5c21b2c7.png)
Jerry's infrared filtering [chapter]

文本对比学习综述

龙蜥开发者说:首次触电,原来你是这样的龙蜥社区? | 第 8 期

数商云:加强供应商管理,助推航空运输企业与供应商高效协同
随机推荐
The real project managers are all closed-loop masters!
Jupiter notebook operation
MySQL日志管理、备份与恢复
Gatling performance test
杰理之增加一个输入捕捉通道【篇】
90% of the project managers have skipped the pit. Are you still in the pit?
The difference between V-IF and v-show
SAP Marketing Cloud 功能概述(三)
鲲鹏arm服务器编译安装PaddlePaddle
Ti Xing Shu'an joined the dragon lizard community to jointly create a network security ecosystem
2022 Quality Officer - Equipment direction - post skills (Quality Officer) recurrent training question bank and online simulation examination
npm包【详解】(内含npm包的开发、发布、安装、更新、搜索、卸载、查看、版本号更新规则、package.json详解等)
Docker installation redis
【无标题】
Solution of channel management system for food and beverage industry: realize channel digital marketing layout
杰理之串口接收 IO 需要设置数字功能【篇】
4个不可不知的“安全左移”的理由
P2PDB 白皮书
win10系统问题
厨卫电器行业B2B交易协同管理平台开发,优化企业库存结构