设计模式

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

整挺好:https://blog.csdn.net/carson_ho/article/details/54910518

单例模式

简介

介绍

单例模式(Singleton),也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

实现思路

一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名 称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们 还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

注意点

单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例, 这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。

优缺点

优点:

  1. 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例。
  2. 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
  3. 提供了对唯一实例的受控访问。
  4. 由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
  5. 允许可变数目的实例。
  6. 避免对共享资源的多重占用。

缺点:

  1. 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
  2. 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  3. 单例类的职责过重,在一定程度上违背了“单一职责原则”。
  4. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

适用场景

单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:

  1. 需要频繁实例化然后销毁的对象。
  2. 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  3. 有状态的工具类对象。
  4. 频繁访问数据库或文件的对象。

以下都是单例模式的经典使用场景:

  1. 资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
  2. 控制资源的情况下,方便资源之间的互相通信。如线程池等。

应用场景举例:

  1. 外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件。
  2. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
  3. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
  4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
  5. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
  6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
  7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
  8. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
  9. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系。
  10. HttpApplication 也是单例的典型应用。

以上来自:https://www.cnblogs.com/damsoft/p/6105122.html

实现方式

饿汉式

这种方法非常简单,因为单例的实例被声明成 static 和 final 变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的

1
2
3
4
5
6
7
8
public class Singleton {
// 类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance(){
return instance;
}
}

缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:譬如 Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用

懒汉式,线程不安全

1
2
3
4
5
6
7
8
9
10
public class Singleton {
private static Singleton instance;
private Singleton () {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance() 的时候,就会创建多个实例

懒汉式,线程安全

为了解决上面的问题,最简单的方法是将整个 getInstance() 方法设为同步(synchronized)

1
2
3
4
5
6
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}

虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁

双重检验锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {  
private volatile static Singleton instance;
private Singleton () {}
public static Singleton getSingleton() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

称其为双重检查锁,是因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了

而 instance = new Singleton() 这句,并非是一个原子操作,事实上在 JVM 中这句话做了下面 3 件事:

  1. 给 instance 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量
  3. 将 instance 对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错

所以需要将 instance 变量声明成 volatile 「禁止重排序」

静态内部类

1
2
3
4
5
6
7
8
9
public class Singleton {  
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton () {}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

静态内部类与外部类没有什么关系,外部类加载的时候,内部类不会被加载,静态内部类只是调用的时候用了外部类的名字而已,所以即使 Singleton 类被加载也不会创建单例对象

静态内部类 SingletonHolder 只有在 getInstance() 方法第一次被调用时,才会被加载,从而初始化它的静态域(创建 Singleton 的实例),因此该种方式实现了懒汉式的单例模式

不仅如此,根据 JVM 本身机制,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性

同时不用 synchronized,所以没有性能缺陷

1
2
3
4
5
6
7
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}

这个写法也是利用类的静态变量的唯一性,不过和上面的写法相比,不能实现懒加载

Enum

用枚举写单例最大的优点是简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public enum Singleton {
INSTANCE {

@Override
protected void read() {
System.out.println("read");
}

@Override
protected void write() {
System.out.println("write");
}

};
protected abstract void read();
protected abstract void write();
}

以上是一个单例枚举的例子,要获取该实例只需要 Singleton.INSTANCE,并且此种方式可以保证该单例线程安全、防反射攻击、防止序列化生成新的实例

反编译后的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public abstract class Singleton extends Enum {

private Singleton(String s, int i) {
super(s, i);
}

protected abstract void read();

protected abstract void write();

public static Singleton[] values() {
Singleton asingleton[];
int i;
Singleton asingleton1[];
System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
return asingleton1;
}

public static Singleton valueOf(String s) {
return (Singleton)Enum.valueOf(singleton/Singleton, s);
}

Singleton(String s, int i, Singleton singleton) {
this(s, i);
}

public static final Singleton INSTANCE;
private static final Singleton ENUM$VALUES[];

static {
INSTANCE = new Singleton("INSTANCE", 0) {

protected void read() {
System.out.println("read");
}

protected void write() {
System.out.println("write");
}

};
ENUM$VALUES = (new Singleton[] {
INSTANCE
});
}
}

看了这个类的真身后,可以知道:

  • 枚举类实现其实省略了 private 类型的构造函数
  • 枚举类的域其实是相应的 enum 类型的一个实例对象
  • 枚举类的域会在 static 方法块中被实例化,也就是说在 enum 被类加载器加载时被实例化,并非懒加载
  • enum 是 abstract 类,所以没法实例化,反射也无能为力
  • 关于线程安全的保证,其实是通过类加载机制来保证的,INSTANCE 是在 static 块中实例化的,JVM 加载类的过程显然是线程安全的
  • 而枚举可以反序列化是因为 Enum 实现了 readResolve 方法

对于一个标准的 enum 单例模式,最优秀的写法还是实现接口的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义单例模式中需要完成的代码逻辑
public interface MySingleton {
void doSomething();
}

public enum Singleton implements MySingleton {
INSTANCE {
@Override
public void doSomething() {
System.out.println("complete singleton");
}
};

public static MySingleton getInstance() {
return Singleton.INSTANCE;
}
}

破坏单例模式的三种方式

  1. 反射
  2. 序列化
  3. 克隆

当单例类被多个类加载器加载,如何还能保持单例

如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些 servlet 容器对每个 servlet 使用完全不同的类装载器,这样的话如果有两个 servlet 访问一个单例类,它们就都会有各自的实例

基于同样的原因,分布式系统和集群系统也都可能出现单例失效的情况

解决方法:用多个类加载器的父类来加载单例类

1
2
3
4
5
6
7
8
9
private static Class getClass(String classname) throws ClassNotFoundException {     
// 线程上下文类加载器,未设置的话默认是应用程序类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

if (classLoader == null)
classLoader = Singleton.class.getClassLoader();

return classLoader.loadClass(classname);
}

单例类防止反序列化

1
2
3
4
5
6
public class Singleton implements java.io.Serializable {
// ...
private Object readResolve() {
return INSTANCE;
}
}

readResolve() 方法可以理解反序列化过程的出口,就是在反序列化完成得到对象前,把这个对象换成我们确定好的那个。

反射破坏单例原则

在单例模式中,只对外提供工厂方法(获取单例),并私有化构造函数,来防止外面多余的创建。对于一般的外部调用来说,私有构造函数已经很安全了。但是一些特权用户可以通过反射来访问私有构造函数,然后打开访问权限 setAccessible(true),就可以访问私有构造函数了,这样破坏了单例的私有构造函数保护,创建了一个新的实例。如果要防御这样的反射侵入,可以修改构造函数,加上第二次实例化的检查,当发生创建第二个单例的请求时会抛出异常。

1
2
3
4
5
6
7
private static int cntInstance = 0;

private Singleton() throws Exception {
if (cntInstance++ > 1) {
throw new Exception("can't create another singleton instance.");
}
}

作者:杰哥长得帅
链接:https://www.jianshu.com/p/ee110d76c42b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

模板方式模式

自己根据 AQS 重写了一个可重入,一个不可重入的显示锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* 非重入
*/
class MutexDemo {
// private static Mutex mutex = new Mutex();
private static aa mutex = new aa();

public static void main(String[] args) {
for (int i = 0; i < 2 ; i++){
Thread thread = new Thread(() -> {
mutex.lock();
try {
System.out.println("Current Thread:" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
mutex.unlock();
}
});
thread.start();
}
}
}

class Mutex implements Lock, java.io.Serializable {

// Our internal helper class
private static class Sync extends AbstractQueuedSynchronizer {
// Reports whether in locked state
protected boolean isHeldExclusively() {
return getState() == 1;
}

protected boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}

protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}

// Provides a Condition
Condition newCondition() {
return new ConditionObject();
}
}

// The sync object does all the hard work. We just forward to it.
private final Sync sync = new Sync();

public void lock() {
sync.acquire(1);
}

public boolean tryLock() {
return sync.tryAcquire(1);
}

public void unlock() {
sync.release(1);
}

public Condition newCondition() {
return sync.newCondition();
}

public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}

public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}


/**
* 可重入
*/
class aa implements Lock,java.io.Serializable{
private static class Sync extends AbstractQueuedSynchronizer{
protected boolean isHeldExclusively(){
return getExclusiveOwnerThread() == Thread.currentThread();
}
protected boolean tryAcquire(int acquires){
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int next = c + acquires;
if (next < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(next);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// Provides a Condition
Condition newCondition() {
return new ConditionObject();
}
}

private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}

@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}

@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}

@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(time));
}

@Override
public void unlock() {
sync.release(1);
}

@Override
public Condition newCondition() {
return sync.newCondition();
}
}

代理设计模式

动态代理

为何动态代理只能代理接口

其实我觉得完全可以代理类,如果你说 ”是因为Proxy$0已经继承了Proxy,java里面单继承所以导致只能代理接口“,那我就得否认你了,因为其实完全可以做到直接让 ”Proxy去代理“,所以,我觉得人家这么设计的原因只有一个:

看了这么多分析的,我觉得这样设计的初衷就是为了套用代理模式模板和基于接口编程规范

静态代理中就是实现接口,再在代理类set进实现类的引用,调用真正代理的方法

就算他不继承Proxy,也不会继承被代理的实现类 ,而是必须有一个代理类的接口去实现它,这是设计的初衷

作者:Forward
链接:https://www.zhihu.com/question/62201338/answer/904553916
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

动态代理的过程是什么

代理的过程在 Proxy.newProxyInstance() 方法中,程序先是进行了验证、优化、缓存、同步、生成字节码和显式类加载等操作,最后调用的 sum.misc.ProxyGenerator.generateProxyClass() 来完成生成字节码的动作,这个方法可以在运行时产生一个描述代理类的字节码数组byte[]。

代理类的实现代码也很简单,它为每一个传入接口中的每一个方法都进行了相应的实现,调用的方法都是在执行 InvocationHandler.invoke() 中的代理逻辑。

动态代理的好处是什么

实现了可以在原始类和接口还未知的情况下,就确定代理类的代理行为,当代理类与原始类脱离直接联系后,就可以灵活的重用于不同的应用场景中了。

什么叫静态代理

静态代理,在编译期间就已经确定了要代理的类和接口,是写死了的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
nterface Person{
public void play();
}



class 静态代理{
public static void main(String[] args) {
Person person = new StaticProxy();
person.play();
}
}

class StaticProxy implements Person{
@Override
public void play() {
Student student = new Student("jerome",10);
student.play();
System.out.println("静态代理");
}
}

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package 设计模式;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class 代理 {
public static void main(String[] args) {
Person person = new Student("jerome",18);
InvocationHandler dynamic = new Dynamic(person);
Person person_proxy = (Person) Proxy.newProxyInstance(person.getClass().getClassLoader(),person.getClass().getInterfaces(),dynamic);
person_proxy.play();
// System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");


}
}

class Dynamic implements InvocationHandler{
// 要代理的对象得传进来
private Object object;
public Dynamic(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//do something
System.out.println("let's begin!");
Object result = method.invoke(object,args);
System.out.println("It's over!");
return result;
}
}

class Student implements Person {
private String name;
private int age;

public Student(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
public void play(){
System.out.println("I just want to play with " + this.name + ", and her age is " + this.age);
}

}

interface Person{
public void play();
}

相关的字节码生成技术—-cglib

相关的 Aop & IOC

Thank you for your accept. mua!
-------------本文结束感谢您的阅读-------------