海南网站建设报价方案,wordpress标签的作用,邓海舟网站建设教程,线上推广专员岗位职责文章目录 前言一、单例模式1.1、饿汉式静态常量单例1.2、饿汉式静态代码块单例1.3、懒汉式单例#xff08;线程不安全#xff09;1.4、懒汉式单例#xff08;线程安全#xff0c;同步代码块#xff09;1.5、懒汉式单例#xff08;线程不安全#xff0c;同步代码块#… 文章目录 前言一、单例模式1.1、饿汉式静态常量单例1.2、饿汉式静态代码块单例1.3、懒汉式单例线程不安全1.4、懒汉式单例线程安全同步代码块1.5、懒汉式单例线程不安全同步代码块1.6、懒汉式单例线程安全双检锁模式1.7、静态内部类单例1.8、枚举单例 二、工厂模式2.1、简单工厂模式2.2、工厂方法模式2.3、抽象工厂模式小结 三、原型模式3.1、如何在原型模式中实现浅拷贝和深拷贝小结 前言 本篇是关于设计模式中单例模式8种包含线程安全非安全的实现、工厂模式3种、以及原型模式深拷贝、浅拷贝的笔记。 一、单例模式 单例模式的核心目的是确保某个类只有一个实例并且提供一个全局访问点来获取该实例。这种模式通常用于需要全局共享资源或者全局配置的场景通常可用在日志管理器只有一个日志实例用于输出日志、线程池避免重复创建等全局资源只需要初始化一次的场景。 如果需要实现单例模式通常需要满足以下三大要素
私有化构造函数防止外部直接创建实例。静态变量保存唯一的实例。公共的静态方法提供对外的访问方式返回该唯一实例。
1.1、饿汉式静态常量单例 最常见的一种饿汉式单例Singleton1的实例是在外部调用Singleton1的getInstance方法时创建的并且Java 的类加载懒加载的也就是说只有当类第一次被引用时才会加载这个类。在加载过程中JVM 会保证静态变量的初始化是线程安全的。 JVM是如何保证静态变量初始化的线程安全 类加载过程是串行的每个类在被加载时会有一个单独的类加载过程加载过程中的所有操作是线程安全的。JVM 会确保对类的初始化只有一个线程可以执行。其他线程会被阻塞直到类初始化完成。 对于静态变量JVM 在类的初始化过程中会执行双检模式当线程第一次访问类时如果该类尚未初始化JVM 会进行初始化并且只有一个线程会执行这个初始化过程。其他线程在初始化过程中会被阻塞直到第一个线程完成初始化类初始化过程保证只会被执行一次避免了多个线程同时初始化实例的问题。 在案例中是在同一个线程中获取了两次INSTANCE实例为何都是同一个 因为静态变量是属于类而不是属于某个方法的静态变量是类的所有实例共享的当 Singleton1 类被加载并初始化时JVM 会创建 INSTANCE 变量并赋值。这一过程只会发生一次。 public class HungryMan1 {public static void main(String[] args) {Singleton1 s1 Singleton1.getInstance();Singleton1 s2 Singleton1.getInstance();System.out.println(s1 s2);}
}/*** 类加载时就创建单例对象* 由JVM保证线程安全性*/
class Singleton1{private Singleton1(){}private final static Singleton1 INSTANCE new Singleton1();public static Singleton1 getInstance(){return INSTANCE;}}
1.2、饿汉式静态代码块单例 相比较于第一种实现区别在于本实现是在静态代码块中完成单例对象初始化的。当调用Singleton2.getInstance()这触发 Singleton2 类的加载类加载过程中JVM 会初始化静态成员变量和静态代码块。
public class HungryMan2 {public static void main(String[] args) {Singleton2 s1 Singleton2.getInstance();Singleton2 s2 Singleton2.getInstance();System.out.println(s1 s2);}
}class Singleton2{private Singleton2(){}private final static Singleton2 INSTANCE;/*** 在静态代码块中完成初始化*/static {INSTANCE new Singleton2();}public static Singleton2 getInstance(){return INSTANCE;}
}
1.3、懒汉式单例线程不安全 该种单例的设计思想是在调用getInstance()时才会主动去创建单例实例。但是下面的实现是存在线程安全问题的如果两个线程同时到达了if块都判断为空就会创建两个不同的实例。
public class LazyMan1 {public static void main(String[] args) throws InterruptedException {Singletion3 s1 Singletion3.getInstance();Singletion3 s2 Singletion3.getInstance();System.out.println(s1 s2);}
}class Singletion3{private Singletion3(){}private static Singletion3 instance;/*** 多线程下存在并发问题* return*/public static Singletion3 getInstance(){if (instance null){instance new Singletion3();}return instance;}
}1.4、懒汉式单例线程安全同步代码块
public class LazyMan2 {public static void main(String[] args) {Singletion4 s1 Singletion4.getInstance();Singletion4 s2 Singletion4.getInstance();System.out.println(s1 s2);}
}class Singletion4{private Singletion4(){}private static Singletion4 instance;/*** 解决线程安全问题但是synchronized是重量级锁效率低* return*/public static synchronized Singletion4 getInstance(){if (instance null){instance new Singletion4();}return instance;}
}1.5、懒汉式单例线程不安全同步代码块 1.4的案例使用重量级锁保证线程安全弊端在于锁的粒度过大。如果缩小锁的范围本案例的写法依旧会存在线程安全问题
public class LazyMan3 {public static void main(String[] args) {Singletion5 s1 Singletion5.getInstance();Singletion5 s2 Singletion5.getInstance();System.out.println(s1 s2);}
}class Singletion5 {private Singletion5() {}private static Singletion5 instance;/*** 降低锁的粒度依旧会存在线程安全问题* 比如AB两个线程在IF处判断都为空都进入了IF块* 虽然只有一个线程能争抢到锁但是在释放锁之后另一个线程也能再次进入同步代码块创建一个新的对象* return*/public static Singletion5 getInstance() {if (instance null) {synchronized (Singletion5.class) {instance new Singletion5();}}return instance;}
}1.6、懒汉式单例线程安全双检锁模式
JUC并发编程java内存模型volatile关键字双检锁单例
public class LazyMan4 {public static void main(String[] args) {}
}class Singletion6 {private Singletion6() {}/*** 这里的volatile 一定要加 第一是避免指令重排序问题第二是将对于instance的更改立刻同步到主存防止缓存问题-**/private static volatile Singletion6 instance;/*** 降低锁的粒度并且进行双重检查* return*/public static Singletion6 getInstance() {if (instance null) {synchronized (Singletion6.class) {if (instance null) {instance new Singletion6();}}}return instance;}
}1.7、静态内部类单例 保证线程安全的方式和饿汉式的类似。
public class StaticInner {public static void main(String[] args) {Singleton7 s1 Singleton7.getInstance();Singleton7 s2 Singleton7.getInstance();System.out.println(s1 s2);}
}class Singleton7{private Singleton7(){}/*** 使用静态内部类的方式静态内部类会在其中方法/变量被调用时初始化*/public static class inner{private static final Singleton7 INSTANCE new Singleton7();}/*** 外部调用Singleton7的getInstance静态方法时初始化inner内部类* JVM在加载类时是线程安全的通过静态内部类只加载一次保证只创建一次外部类的实例* return*/public static Singleton7 getInstance(){return inner.INSTANCE;}}1.8、枚举单例 最后一种是枚举单例也是推荐使用的一种方式。上面所有的单例即使是线程安全的也会有可能因为使用反射而被破坏。 枚举单例为什么能保证线程安全 在类加载时JVM 会创建枚举实例并将其保存在内存中。在整个应用生命周期内枚举的实例是唯一的JVM 会在类加载时保证它的线程安全和单例性。 当 Singleton.INSTANCE 被访问时枚举实例已经由 JVM 在类加载时创建好并且只会创建一次。 public class EnumSingleton {public static void main(String[] args) {Singleton8 s1 Singleton8.SINGLETON;Singleton8 s2 Singleton8.SINGLETON;System.out.println(s1 s2);}
}/*** 枚举单例 没有线程安全问题也不会导致通过暴力反射破坏单例* 前面的方式虽然将构造私有化但是都是可以通过反射破解的*/
enum Singleton8{SINGLETON;}二、工厂模式 工厂模式的核心思想在于将对象的实例化过程封装起来使得代码不需要直接调用构造方法来创建对象而是通过一个工厂方法来获取实例客户端代码不需要关心如何创建对象只需要关心如何使用对象。七大原则中的迪米特原则依赖倒置原则即将对象的创建过程和使用过程分离客户端可以获取到对象而不需要知道对象的具体创建细节。
2.1、简单工厂模式 假设现在要模拟一个制作披萨的过程披萨的种类有Cheess和Greek两种制作的过程有prepare准备bake烘烤cut切割box打包。由于不同的披萨准备原材料的方式是不一样的可以这样设计
public abstract class Pizza {private String name;public void setName(String name) {this.name name;}/*** 每种披萨的准备过程是不一样的留给子类去实现*/public void prepare() {}public void bake() {System.out.println(烘烤 name);}public void cut() {System.out.println(切割 name);}public void box() {System.out.println(打包 name);}}
public class GreekPizza extends Pizza {Overridepublic void prepare() {System.out.println(制作希腊披萨 准备材料);}
}
public class CheesePizza extends Pizza {Overridepublic void prepare() {System.out.println(制作奶酪披萨 准备材料);}
}再用一个类模拟订购披萨的过程
public class OrderPizza {public OrderPizza() {Pizza pizza null;Scanner sc new Scanner(System.in);while (true){System.out.println(***********请输入需要制作的pizza***********);String next sc.next();//调用工厂的方法创建具体的实例PizzaFactory pizzaFactory new PizzaFactory();pizza pizzaFactory.createPizza(next);if (pizza null){break;}pizza.bake();pizza.cut();pizza.box();}sc.close();}
}将制作具体披萨的代码放置到了工厂类中这样有什么好处如果有多个订购披萨的类并且我现在要加一个披萨的种类如果制作具体披萨的代码还是放置在每个订购披萨的类中那么所有的类都需要进行修改扩展性很差。
/*** 创建Pizza的工厂类*/
public class PizzaFactory {public Pizza createPizza(String type) {Pizza pizza;if (type.equals(Cheese)){pizza new CheesePizza();pizza.setName(奶酪披萨);}else if (type.equals(Greek)){pizza new GreekPizza();pizza.setName(希腊披萨);}else {return null;}return pizza;}
}
/*** Pizza店*/
public class PizzaStore {public static void main(String[] args) {new OrderPizza();}
}2.2、工厂方法模式 工厂方法模式是对简单工厂模式的一种改进。假设需求发生变更除了披萨有不同的种类还有不同地区供应披萨比如北京的Cheess披萨伦敦的Greek披萨…等简单工厂模式不适合这种复杂的需求我们可以用工厂方法模式将一个大的工厂拆分成多个子工厂实现
public abstract class Pizza {private String name;public void setName(String name) {this.name name;}/*** 每种披萨的准备过程是不一样的留给子类去实现*/public void prepare() {}public void bake() {System.out.println(烘烤 name);}public void cut() {System.out.println(切割 name);}public void box() {System.out.println(打包 name);}}class LDGreekPizza extends Pizza {Overridepublic void prepare() {System.out.println(制作伦敦希腊披萨 准备材料);}
}class LDCheesePizza extends Pizza{Overridepublic void prepare() {System.out.println(制作伦敦奶酪披萨 准备材料);}
}class BJGreekPizza extends Pizza {Overridepublic void prepare() {System.out.println(制作北京希腊披萨 准备材料);}
}class BJCheesePizza extends Pizza {Overridepublic void prepare() {System.out.println(制作北京奶酪披萨 准备材料);}
}对工厂进行拆分
/*** 订购pizza*/
public abstract class OrderPizza {/*** 该方法留给具体的 伦敦 北京 披萨的类去实现做自己类型的pizza* param type* return*/public abstract Pizza createPizza(String type);public OrderPizza() {Pizza pizza null;Scanner sc new Scanner(System.in);while (true){System.out.println(***********请输入需要制作的pizza***********);String next sc.next();//制作pizzapizza createPizza(next);if (pizza null){break;}pizza.bake();pizza.cut();pizza.box();}sc.close();}
}class LDOrderPizza extends OrderPizza{public LDOrderPizza() {super();}Overridepublic Pizza createPizza(String type) {Pizza pizza;if (type.equals(Cheese)){pizza new CheesePizza();pizza.setName(伦敦奶酪披萨);}else if (type.equals(Greek)){pizza new GreekPizza();pizza.setName(伦敦希腊披萨);}else {return null;}return pizza;}
}class BJOrderPizza extends OrderPizza {public BJOrderPizza() {super();}Overridepublic Pizza createPizza(String type) {Pizza pizza;if (type.equals(Cheese)){pizza new CheesePizza();pizza.setName(北京奶酪披萨);}else if (type.equals(Greek)){pizza new GreekPizza();pizza.setName(北京希腊披萨);}else {return null;}return pizza;}
}在模拟订购披萨时只需要创建具体实现类的对象即可
/*** Pizza店*/
public class PizzaStore {public static void main(String[] args) {//创建具体的实现类new BJOrderPizza();}
}2.3、抽象工厂模式 抽象工厂模式是工厂方法模式的进一步扩展将工厂抽象成两层接口层抽象工厂实现类具体负责生产各自产品的工厂
/*** 抽象工厂模式* 侧重于将工厂分为了多层*/
public interface AbsFactory {Pizza createPizza(String type);}class BJPizzaFactory implements AbsFactory {Overridepublic Pizza createPizza(String type) {Pizza pizza;if (type.equals(Cheese)){pizza new CheesePizza();pizza.setName(北京奶酪披萨);}else if (type.equals(Greek)){pizza new GreekPizza();pizza.setName(北京希腊披萨);}else {return null;}return pizza;}
}class LDPizzaFactory implements AbsFactory {Overridepublic Pizza createPizza(String type) {Pizza pizza;if (type.equals(Cheese)){pizza new CheesePizza();pizza.setName(伦敦奶酪披萨);}else if (type.equals(Greek)){pizza new GreekPizza();pizza.setName(伦敦希腊披萨);}else {return null;}return pizza;}
}只需要实例化对应的工厂类实现即可获得相应的对象。 /*** Pizza店*/
public class PizzaStore {public static void main(String[] args) {new OrderPizza(new BJPizzaFactory());}
}class OrderPizza {public OrderPizza(AbsFactory factory) {setFactory(factory);}private void setFactory(AbsFactory factory) {Pizza pizza null;Scanner sc new Scanner(System.in);while (true){System.out.println(***********请输入需要制作的pizza***********);String type sc.next();//制作pizza 由传入的AbsFactory子类类型 去路由到不同的子类pizza factory.createPizza(type);if (pizza null){break;}pizza.bake();pizza.cut();pizza.box();}sc.close();}
}
小结 简单工厂模式可以理解成仅仅是把具有共性的创建对象的代码抽取到了一个类中无法应对复杂的业务。而工厂方法模式是对简单工厂模式的一种改进将产品的创建过程委托给子类来解决简单工厂模式的缺点。会将子类进行分类。每个子类负责创建一个特定类型的产品客户端只需要通过工厂方法来获取对象。抽象工厂模式可以看做是简单工厂模式和工厂方法模式的结合既包含了抽象工厂和实现工厂也包含了抽象产品和实现产品。
简单工厂模式适合产品种类较少的情况。工厂方法模式适合产品种类较多且需要通过继承进行扩展的情况。抽象工厂模式适合需要创建一系列相关产品的情况。
三、原型模式 原型模式的核心思想在于通过复制现有的对象来创建新对象而不是通过使用构造函数直接创建。这种模式通过克隆现有对象来生成新实例从而避免了重复创建对象的复杂性和性能开销特别适合在需要频繁创建类似对象的场景中。 原型模式的结构通常包括以下几个角色
Prototype原型接口定义了一个抽象的克隆方法通常是 clone() 方法供具体类实现。
public interface Prototype {Prototype clone(); // 克隆方法
}ConcretePrototype具体原型类实现了原型接口的具体类具体定义克隆方法返回一个自己对象的副本。
public class ConcretePrototype implements Prototype {private String name;public ConcretePrototype(String name) {this.name name;}public String getName() {return name;}Overridepublic Prototype clone() {// 通过构造函数创建新的对象return new ConcretePrototype(this.name);}
}
Client客户端使用原型对象并通过克隆方法来创建新的对象。
public class Client {public static void main(String[] args) {// 创建一个原型对象ConcretePrototype prototype new ConcretePrototype(Prototype1);// 通过克隆方法创建一个新对象ConcretePrototype clonePrototype (ConcretePrototype) prototype.clone();// 输出原型对象的名称System.out.println(Original: prototype.getName());System.out.println(Clone: clonePrototype.getName());System.out.println(prototype clonePrototype);}
} 同时JDK自带的clone方法也是原型模式的体现默认是浅拷贝需要实现Cloneable接口。 什么是浅拷贝和深拷贝 浅拷贝对于基本数据类型拷贝的是值。对于引用类型拷贝的是引用即地址源对象和目标对象的引用类型成员指向同一个内存位置。如果源对象或目标对象对引用类型的成员进行修改另一对象的对应成员也会发生变化。 深拷贝对于基本数据类型拷贝的是值。对于引用类型会递归地拷贝引用类型所指向的对象。源对象和目标对象不会共享任何引用类型成员修改一个对象的引用类型成员不会影响另一个对象。也是不可变类设计的一个要素。 JUC并发编程不可变类设计 3.1、如何在原型模式中实现浅拷贝和深拷贝 浅拷贝通常通过调用 clone() 方法来实现
public class ConcretePrototype implements Cloneable {private String name;private Address address;public ConcretePrototype(String name, Address address) {this.name name;this.address address;}public String getName() {return name;}public void setName(String name) {this.name name;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address address;}Overridepublic ConcretePrototype clone() {try {return (ConcretePrototype) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return null;}Overridepublic String toString() {return ConcretePrototype{ name name \ , address address };}
}class Address {private String street;public Address() {}public Address(String street) {this.street street;}/*** 获取* return street*/public String getStreet() {return street;}/*** 设置* param street*/public void setStreet(String street) {this.street street;}public String toString() {return Address{street street };}
}
public class ShallowCopy {public static void main(String[] args) {Address address new Address();address.setStreet(xx街道);ConcretePrototype original new ConcretePrototype(测试, address);ConcretePrototype copy original.clone();System.out.println(原件 original.toString());System.out.println(复印件 copy.toString());System.out.println(修改复印件的Address中的Street字段);copy.getAddress().setStreet(yy街道);System.out.println(原件 original.toString());System.out.println(复印件 copy.toString());}
}最终的结果是复印件引用的Address中的字段值发生改变原件的同步更新 原件ConcretePrototype{name‘测试’, addressAddress{street xx街道}} 复印件ConcretePrototype{name‘测试’, addressAddress{street xx街道}} 修改复印件的Address中的Street字段 原件ConcretePrototype{name‘测试’, addressAddress{street yy街道}} 复印件ConcretePrototype{name‘测试’, addressAddress{street yy街道}} 深拷贝的区别在于在调用clone()方法进行浅克隆后还需要对其中的引用类型创建新的实例赋值给复制品的引用类型字段这样原件和复印件的引用类型字段的引用指向的地址就不相同
public class ConcretePrototype implements Cloneable {//......Overridepublic ConcretePrototype clone() {try {// 深拷贝创建新的 Address 对象ConcretePrototype clone (ConcretePrototype) super.clone();// 手动创建新的引用使原件和复印件的地址不相同clone.address new Address(this.address.getStreet());return clone;} catch (CloneNotSupportedException e) {e.printStackTrace();}return null;}Overridepublic String toString() {return ConcretePrototype{ name name \ , address address };}
} 原件ConcretePrototype{name‘测试’, addressAddress{street xx街道}} 复印件ConcretePrototype{name‘测试’, addressAddress{street xx街道}} 修改复印件的Address中的Street字段 原件ConcretePrototype{name‘测试’, addressAddress{street xx街道}} 复印件ConcretePrototype{name‘测试’, addressAddress{street yy街道}} 小结 原型模式的适用场景
创建对象的代价较大当对象的创建成本高如涉及到数据库操作、网络调用等而且创建出来的对象大部分都相似时原型模式可以通过复制现有对象来降低性能开销。和单例模式的区别在于因为业务需求该对象必须要创建多份。需要大量相似对象如果需要创建很多类似的对象通过克隆现有对象可以避免反复执行相同的构造逻辑。避免重复代码当多个对象具有相似的构建过程时原型模式能够避免重复编写对象创建的代码。