当前位置:网站首页>常见的单例模式&简单工厂

常见的单例模式&简单工厂

2022-06-24 13:02:00 一麟yl

目录

前言:

单例模式:

一,饿汉模式

二,懒汉模式

1.单向判断简易法

2.双重校验锁法

3.通过静态内部类实例化该类

4.枚举类法:

工厂模式

1.工厂模式的概念:

2.工厂模式的使用场景:

3.工厂方法:


前言:

为什么我们要学习设计模式:设计模式(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();
	}

}

控制台打印出来的结果:

以上我们的简易工厂模式。这样来讲是不是更加通俗易懂了。

下期我会给大家带来抽象工厂以及更多的知识,希望我的理解对大家有所帮助。我们下期再见!

原网站

版权声明
本文为[一麟yl]所创,转载请带上原文链接,感谢
https://blog.csdn.net/m0_67376124/article/details/125370276