[Java][设计模式]

news/2024/10/5 14:54:49/文章来源:https://www.cnblogs.com/DCFV/p/18287905

代理模式PROXY

静态代理

定义一个代理规范,规定代理和目标对象实现同样的方法
举个例子,银行柜员和银行都要有取钱的方法,我们才能通过银行柜员去取银行的钱

public interface Proxy {void withdraw();
}
public class Bank implements Proxy{@Overridepublic void withdraw(){System.out.println("银行正在为你取钱");}
}
public class Stuff implements Proxy{Bank bank;@Overridepublic void withdraw() {if(bank == null){bank = new Bank();}System.out.println("银行柜员正在为你取钱");bank.withdraw();System.out.println("银行柜员取钱完成");}
}
public class User {public static void main(String[] args) {Stuff stuff = new Stuff();stuff.withdraw();}
}

动态代理

首先我们编写一个目标类,也就是真正干活的类

public class TrainStation implements ProxyMethod {public TrainStation() {}@Overridepublic void sellTicket() {System.out.println("火车站正在出票");}@Overridepublic boolean refund() {System.out.println("火车站正在退票");return true;}
}

然后定义一个接口,规定我们想要代理的方法


public interface ProxyMethod {void sellTicket();boolean refund();}

编写代理工具类,负责生成代理对象

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class ProxyUtil {public static ProxyMethod creatProxy(TrainStation trainStation) {ProxyMethod proxyMethod = (ProxyMethod) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),new Class[]{ProxyMethod.class},new InvocationHandler() {@Override// 当我们使用代理对象public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (method.getName().equals("sellTicket")) {System.out.println("代理正在买票");}if (method.getName().equals("refund")) {System.out.println("代理正在退票");}Object res = method.invoke(trainStation, args);if (method.getName().equals("sellTicket")) {System.out.println("代理正在出票");}if (method.getName().equals("refund")) {System.out.println("代理正在退票");}return res;}});return proxyMethod;}
}

最后client测试

public class Client {public static void main(String[] args) {TrainStation trainStation = new TrainStation();ProxyMethod p = ProxyUtil.creatProxy(trainStation);p.sellTicket();boolean b = p.refund();System.out.println(b);}
}

动态代理分为两种一种是jdk代理

JDK动态代理是Java标准库中提供的一种代理方式,它可以在运行时动态生成一个代理对象,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。

JDK动态代理通过反射机制实现代理功能,其原理分为以下几个步骤:

创建实现InvocationHandler接口的代理类工厂:在调用Proxy类的``静态方法newProxyInstance时,会动态生成一个代理类。该代理类实现了目标接口,并且持有一个InvocationHandler类型的引用。 InvocationHandler接口:InvocationHandler是一个接口,它只有一个方法invoke。在代理对象的方法被调用时,JVM会自动调用代理类的invoke`方法,并将被调用的方法名、参数等信息传递给该方法。
调用代理对象的方法:当代理对象的方法被调用时,JVM会自动调用代理类的invoke方法。在invoke方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等。
invoke方法调用:在invoke方法中,通过反射机制调用目标对象的方法,并返回方法的返回值。在调用目标对象的方法前后,可以执行额外的逻辑。

单例模式

懒汉式,线程不安全

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

懒汉式,线程不安全

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

懒汉式,线程安全

public class Singleton {public static Singleton singleton;private Singleton(){}// 主要就是加了锁机制,避免两个线程都检查到null,然后分别创建了singletonpublic static synchronized Singleton getInstance(){if (singleton == null){singleton = new Singleton();}return singleton;}
}

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

饿汉式,线程安全

public class Singleton {// static 一方面保证了instance在类加载的时候就会生成// 另一方面保证了这个instance是属于这个类的private static Singleton singleton = new Singleton();private Singleton(){}public static Singleton getInstance(){return singleton;}
}

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

双检锁/双重校验锁(DCL,即 double-checked locking),线程安全,效率高

首先来看一下单线程懒汉模式的缺点

public class Singleton {// 声明静态变量public static Singleton singleton;// 私有化构造方法,使得外界不能调用newprivate Singleton(){}// 构造一个静态方法,属于这个类的静态方法// 在这个方法中检查类的静态变量是否已经生成// 如果没有生成,在方法中生成public static Singleton getInstance(){if (singleton == null){singleton = new Singleton();}return singleton;}
}

一个情况是,如果有一个线程A检查singleton == null条件成立,此时另一个线程B也检查singleton == null条件成立。
B线程new了一个singleton,同样的A线程也new一个singleton,这样就不符合单例模式了。

另外一个情况是,如果线程A判断singleton == null条件成立,
于是开始new singleton(),但是new的过程并不是一气呵成的,
他需要三个步骤,分别是,分配空间,初始化Singleton,把内存空间地址赋给singleton,这三个步骤有可能被指令重排序,即先发生分配空间,把内存空间地址赋给singleton,然后再初始化,

如果分配空间,把内存空间地址赋给singleton,这两步完成之后,B线程进来判断singleton == null不成立,虽然对象没有完全建立起来,但是这个引用已经有值了,直接把未完全new的对象返回了,这是不对的。

首先我们来解决第一个情况,为了避免两个线程进入都进入判断,我们可以给整个函数加锁

public class Singleton {// 声明静态变量public static Singleton singleton;// 私有化构造方法,使得外界不能调用newprivate Singleton(){}// 构造一个静态方法,属于这个类的静态方法// 在这个方法中检查类的静态变量是否已经生成// 如果没有生成,在方法中生成public static synchronized Singleton getInstance(){if (singleton == null){singleton = new Singleton();}return singleton;}
}

但是实际上这是没有必要的,因为如果对整个函数加锁,在singleton已经存在之后,众多的读操作会被阻塞在函数之外,排队等待进去函数,这是不必要的,不需要阻塞单例生成之后的读操作。

可以采用另一种写法

public class Singleton {// 声明静态变量public static Singleton singleton;// 私有化构造方法,使得外界不能调用newprivate Singleton(){}// 构造一个静态方法,属于这个类的静态方法// 在这个方法中检查类的静态变量是否已经生成// 如果没有生成,在方法中生成public static Singleton getInstance(){synchronized(Singleton.class){if (singleton == null){singleton = new Singleton();}}return singleton;}
}

这样写还有一个问题,就是线程确实不需要在函数外进行等待,但是进入函数之后,线程还是在继续等待,等待判断singleton == null,
这样的写法保证了判断和产生新对象是一气呵成的,但是效率不够

public class Singleton {private volatile static Singleton singleton;private Singleton (){}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {// 第一个进来的线程会看到null,后续线程不会看到nullif (singleton == null) {singleton = new Singleton();}}}return singleton;}
}

这样写一方面保证了效率,因为singleton == null不成立的时候,也就是单例已经产生之后,直接可以进行读操作,不需要排队等待

另一方面也解决了重复new的问题,因为在单例未生成的时候,需要排队等待,不会产生两个线程都判断singleton == null,都产生单例的情况。

情况一到此就解决了,那么情况二呢?

这时候如果线程A正在new新对象,并且发生了执行重新排序,已经完成了分配空间,把地址赋值给引用两个操作,这时线程B走到了第一个singleton == null,发现不成立,直接取走了单例,但是这个时候的单例并不是一个合法的单例。如何解决这个问题呢?

可以使用关键字volatile

volatile有两个作用,

其一,加volatile的对象,如果想要读取这个对象,需要从主内存读取一份,保证读到的是最新的对象,如果对这个对象进行了写操作,要立刻写回主内存,保证主内存中一直都是最新的对象。

其二,禁止指令重排序,这样就保证了new对象的工程中最后才对引用singleton赋值,避免了判断singleton == null不成立,但是读取的时候读到的是一个不完全的singleton。

静态内部类,线程安全,延迟加载

是否 Lazy 初始化:

是否多线程安全:

实现难度:一般

描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

public class Singleton {private static class SingletonHelp{private static final Singleton singleton = new Singleton();}private Singleton(){}public static Singleton getSingleton(){return SingletonHelp.singleton;}
}

这种方法一方面克服了传统的饿汉式外部类加载就产生单例的浪费,因为singleton类加载的时候内部类并没有被加载,所以内部类的静态变量并未生成。

另一方面,这种方法也保证了线程安全,因为单例的生成是类加载的时候一次性生成的,不存在线程并发的问题。

PS:静态内部类

静态内部类(static nested class)的类加载过程与普通类有一些相似之处,但也有其独特之处。以下是静态内部类的类加载过程的详细描述:

1. 类加载时机
  • 独立加载:静态内部类是属于外部类的一个静态成员,但它和外部类是独立的。因此,静态内部类不会在外部类加载时自动加载,只有在静态内部类本身被使用时才会加载。
  • 使用场景:静态内部类会在以下情况发生时加载:
    • 静态内部类的静态成员(字段、方法)被调用时。
    • 静态内部类的实例被创建时。
    • 静态内部类的静态初始化块被执行时。
2. 类加载步骤

与普通类的加载过程相同,静态内部类的加载过程包括以下几个步骤:

  1. 加载(Loading)

    • 虚拟机通过类加载器读取静态内部类的.class文件,将其字节码加载到内存中,并创建一个Class对象来表示该类。
  2. 连接(Linking)

    • 验证(Verification):确保静态内部类的字节码文件格式正确,满足Java语言规范的要求。
    • 准备(Preparation):为静态内部类的静态变量分配内存,并将其初始化为默认值。
    • 解析(Resolution):将静态内部类的符号引用转换为直接引用。
  3. 初始化(Initialization)

    • 执行静态内部类的静态初始化块(如果有)和静态变量的初始化赋值语句。
3. 特点和注意事项
  • 独立性:静态内部类与外部类是相对独立的。虽然静态内部类可以访问外部类的静态成员,但它们的类加载过程是独立的。
  • 访问权限:静态内部类可以访问外部类的所有静态成员,包括私有静态成员。
  • 外部类的引用:静态内部类不持有对外部类实例的引用,因为它不依赖于外部类的实例。这与非静态内部类(成员内部类)不同,后者需要持有外部类的实例引用。
代码示例

以下是一个包含静态内部类的简单Java代码示例:

public class OuterClass {private static String outerStaticField = "Outer Static Field";static class StaticNestedClass {static {System.out.println("Static Nested Class Initialized");}void display() {System.out.println("Accessing: " + outerStaticField);}}public static void main(String[] args) {System.out.println("Main Method Start");StaticNestedClass nestedObject = new StaticNestedClass();nestedObject.display();}
}
说明
  • 在上述代码中,静态内部类StaticNestedClass在其静态成员被访问或者实例被创建时才会被加载和初始化。
  • StaticNestedClass的静态初始化块会在第一次使用时执行,打印出"Static Nested Class Initialized"。
  • 外部类的静态字段outerStaticField可以被静态内部类直接访问。

总结来说,静态内部类的类加载过程与普通类的加载过程类似,只有在静态内部类的成员被访问或实例被创建时,静态内部类才会被加载和初始化。

枚举,最优解

JDK 版本:JDK1.5 起

是否 Lazy 初始化:

是否多线程安全:

实现难度:

描述:这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。

public enum Singleton {SINGLETON;public void show() {System.out.println("枚举类实现单例");}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/739532.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第一次学习Java的碎碎念

2024年夏新的学习开始了; 今天做了什么? 在B站上收藏了黑马程序员学习Java的教学视频,观看了几篇入门教程,暂时学会了如何打开CMD,以及几个常见的CMD命令,例如盘符名称:、dir、cd目录、cd..、cls、exit等等,做了一个练习(利用cmd打开qq),学会了如何把应用程序的路径…

测试标题

测试摘要\[a /ge b /eq c \]

Java反射与Fastjson的危险反序列化

Preface 在前文中,我们介绍了 Java 的基础语法和特性和 fastjson 的基础用法,本文我们将深入学习fastjson的危险反序列化以及预期相关的 Java 概念。 什么是Java反射? 在前文中,我们有一行代码 Computer macBookPro = JSON.parseObject(preReceive,Computer.class); 这行代…

Win10双屏设置 之 鼠标不能从中间划过 问题解决

Win10双屏设置 之 鼠标不能从中间划过解决-百度经验 (baidu.com)

比赛获奖的武林秘籍:03 好的创意选取-获得国奖的最必要前提

本文主要介绍了大学生电子计算机类比赛和创新创业类比赛创意选取的重要性,并列举了好的创意选取和坏的创意选取的例子,同时说明了好的创意选取具有哪些特点,同时对常见的创意选取途径与来源进行了基本介绍。比赛获奖的武林秘籍:03 好的创意选取-获得国奖的最必要前提 摘要 …

阶段测试

Sre网络班阶段测试 一:用sed 命令修改/etc/fstab文件,删除文件中的空行,注释行,并保留文件备份(7分) 答案写这里:二: 用 find 命令查找出 /var/ 目录中大于1M且以db结尾的文件(7分) 答案写这里:三: 先判断当前主机是否安装了nginx包,如果没安装,则执行命令安装,…

时间序列分析专题——利用SPSS专家建模器进行建模

SPSS的专家建模器可以自动识别数据,给出最适合的模型,本章通过三个例题介绍如何使用SPSS实现时间序列分析。由于本人对时间序列分析的理解尚浅,做出模型后在论文上的呈现形式需要取查阅资料,以便更好地在论文上呈现 在此之前,我们还需要了解时间序列分析的一些基础的名词 …

如何在ubuntu上设置清华源

如何在ubuntu上设置清华源 apt介绍 apt(Advanced Packaging Tool)是一个在 Debian 和 Ubuntu 中的 Shell 前端软件包管理器。 apt 命令提供了查找、安装、升级、删除某一个、一组甚至全部软件包的命令,而且命令简洁而又好记。 apt 命令执行需要超级管理员权限(root)。 操作 …

c++ u7-02-高精度乘法

本节课作业: 链接:https://pan.baidu.com/s/13-FC86jSHGziRDA8lqzimg?pwd=owv1 提取码:owv1高精度乘法    #include<iostream> #include<cstdio> #include<cstring> using namespace std; string x , y; int a[50010] , b[50010] , c[50010…

node-red的基本指令

1. inject->debug输入到输出,调试结果在右边如果选择时间戳的话,可以选择立即执行,或者周期性,持续执行inject除了时间戳还有一些其他输入项可以选择inject选择json文件输出写好json文件之后点击格式化json,可以校对文件格式payload.number可以让输出只输出number的内容…

lombokjunit

lombok&junit 1 lombok先去官网或者maven仓库下载jar包https://mvnrepository.com/导入第三方包到项目中右键lib文件夹,点击add as library默认jvm不解析第三方注解,需要手动开启使用//@Setter // 生成set方法 1 //@Getter // 生成get方法 2 //@ToString // 生…