单例模式
它属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
根据定义可以看到,单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。
优点:在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例以及避免对资源的多重占用。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
几种实现方式
懒汉式,线程不安全
这种实现最大的问题就是不支持多线程。因为没有加锁synchronized
,所以严格意义上它并不算单例模式。
Singleton
类刚刚被初始化,instance
对象还是空,这时候两个线程同时访问getInstance
方法,因为Instance
是空,所以两个线程同时通过了条件判断,开始执行new
操作,这样一来,显然instance
被构建了两次
1 | public class Singleton { |
懒汉式,线程安全
第一次调用才初始化,避免内存浪费。必须加锁 synchronized
才能保证单例,但加锁会影响效率。
1 | public class Singleton { |
饿汉式,线程安全
这种方式比较常用,但容易产生垃圾对象。它没有加锁,执行效率会提高。但是类加载时就初始化,浪费内存。
1 | public class Singleton { |
典型的双重检查锁定
1 | class Singleton{ |
这是一种懒汉的单例模式,使用时才创建对象,而且为了避免初始化操作的指令重排序,给 instance 加上了volatile。它只是在第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。
其中 volatile 的作用是啥?
- 禁止指令重排序:我们知道 new Singleton() 是一个非原子操作,编译器可能会重排序【构造函数可能在整个对象初始化完成前执行完毕,即赋值操作(只是在内存中开辟一片存储区域后直接返回内存的引用)在初始化对象前完成】。而线程 B 在线程 A 赋值完时判断 instance 就不为 null 了,此时 B 拿到的将是一个没有初始化完成的半成品。
- 保证可见性:线程 A 在自己的工作线程内创建了实例,但此时还未同步到主存中;此时线程 B 在主存中判断instance 还是 null,那么线程 B 又将在自己的工作线程中创建一个实例,这样就创建了多个实例。
为什么双重检查?
考虑这样一种情况,就是有两个线程同时到达,即同时调用 getInstance() 方法,
此时由于 instance== null ,所以很明显,两个线程都可以通过第一重的 instance== null ,
进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重 instance== null ,
而另外的一个线程则会在 lock 语句的外面等待。
而当第一个线程执行完 new SingleTon()语句后,便会退出锁定区域,此时,第二个线程便可以进入lock 语句块,
此时,如果没有第二重 instance== null 的话,那么第二个线程还是可以调用 new SingleTon()语句,
这样第二个线程也会创建一个 SingleTon 实例,这样也还是违背了单例模式的初衷的。
在没有第一重 instance== null 的情况下,也是可以实现单例模式的?那么为什么需要第一重 instance== null呢?
涉及一个性能问题了,因为对于单例模式的话,newSingleTon()只需要执行一次就 OK 了,
而如果没有第一重 instance== null 的话,每一次有线程进入 getInstance()时,均会执行锁定操作来实现线程同步,
这是非常耗费性能的,而如果我加上第一重 instance== null 的话,
那么就只有在第一次,也就是 instance==null 成立时的情况下执行一次锁定以实现线程同步,
而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock语句块了,这样就可以解决由线程同步带来的性能问题了。
一个单例有两个变量的单例模式
工厂模式
简单工厂
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
优点:一个调用者想创建一个对象,只要知道其名称就可以了。 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
实例:创建一个可以绘制不同形状的绘图工具,可以绘制圆形,正方形,三角形,每个图形都会有一个draw()
方法用于绘图。
首先定义一个接口,作为这三个图像的公共父类,并在其中声明一个公共的draw
方法。
1 | public interface Shape { |
下面就是编写具体的图形:圆形,正方形,三角形,每种图形都实现Shape
接口
1 | // 圆形 |
1 | // 正方形 |
1 | // 三角形 |
再是工厂类的具体实现
1 | public class ShapeFactory { |
在这个工厂类中通过传入不同的type
可以new
不同的形状,返回结果为Shape
类型,这个就是简单工厂核心的地方了。
客户端使用
1 | // 画圆形 |
工厂模式
工厂方法模式是简单工厂的仅一步深化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。也就是说每个对象都有一个与之对应的工厂。
实例:现在需要设计一个这样的图片加载类,它具有多个图片加载器,用来加载jpg
,png
,gif
格式的图片,每个加载器都有一个read()
方法,用于读取图片。
首先完成图片加载器的设计,编写一个加载器的公共接口。
1 | public interface Reader { |
完成各个加载器的代码:
1 | // Jpg图片加载器 |
按照定义所说定义一个抽象的工厂接口ReaderFactory
1 | public interface ReaderFactory { |
接下来我们把上面定义好的每个图片加载器都提供一个工厂类,这些工厂类实现了ReaderFactory
。在每个工厂类中我们都通过复写的getReader()
方法返回各自的图片加载器对象。
1 | // Jpg加载器工厂 |
客户端使用:
1 | // 读取Jpg |
抽象工厂模式和工厂方法模式区别
工厂模式:定义一个用于创建对象的借口,让子类决定实例化哪一个类
抽象工厂模式:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类
代理模式
代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
优点:职责清晰;高扩展性;智能化。
缺点:由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
实例:将创建一个 Image
接口和实现Image
接口的实体类。ProxyImage
是一个代理类,减少RealImage
对象加载的内存占用。
ProxyPatternDemo
,我们的演示类使用 ProxyImage
来获取要加载的 Image
对象,并按照需求进行显示。
先创建一个接口:
1 | public interface Image { |
创建实现接口的实体类。
1 | public class RealImage implements Image { |
1 | public class ProxyImage implements Image{ |
当被请求时,使用ProxyImage
来获取RealImage
类的对象。
1 | public class ProxyPatternDemo { |