设计模式
创建型模式:
单例模式
单例模式的三种情况:饿汉式、懒汉式、双重检测(DCL)
我先按照自己的思路把今天复习的内容写一下:
首先是单例模式核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点常见应用场景:
- Windows的Task Manager(任务管理器)就是很典型的单例模式
- windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
- 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
- Application 也是单例的典型应用(Servlet编程中会涉及到)
- 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
- 在servlet编程中,每个Servlet也是单例
- 在spring MVC框架/struts1框架中,控制器对象也是单例
接下来介绍三种实现情况:
懒汉式
public class SingletonDemo01 {
private static SingletonDemo01 s;//声明变量后,不马上去new一个实例。
private SingletonDemo01(){} //私有化构造器
public static synchronized SingletonDemo01 getInstance(){//提供一个全局访问方法
if(s==null){ //位置1
s = new SingletonDemo01();//位置2
}
return s;
}
}
优点:延迟加载, 懒加载! 真正用的时候才加载!
问题:资源利用率高了。但是,每次调用getInstance()方法都要同步,并发效率较低。
为什么说需要同步呢?假设没有synchronize修饰SingletonDemo01。我们可以分析一下上面的代码:假设存在两个线程,线程1执行到位置1处,将要执行位置2处的代码,还没有执行,此时CPU时间片换到了线程2,线程2执行位置1处的代码,做判断,因为之前线程1并没有去new对象因此s为null,此时线程2会创建一个对象。然后,CPU回到线程1,线程1会继续执行,然后又创建一个新的对象。此时违背单例模式原则。因此需要加上synchronize关键字修饰。
双重检测
双重检测的出现:是基于懒汉式的同步效率问题,我们的目的是只创建一个实例,位置2处代码只会执行一次,这个地方需要同步,后面创建了实例之后,s非空就会直接返回对象引用。synchronize修饰方法,会锁住整个类,效率低。考虑使用同步块。如下所示:
public class SingletonDemo01 {
private static SingletonDemo01 s;//声明变量后,不马上去new一个实例。
private SingletonDemo01(){} //私有化构造器
public static SingletonDemo01 getInstance(){//提供一个全局访问方法
if(s==null){
synchronized(SingletonDemo01.class){
s = new SingletonDemo01();
}
}
return s;
}
}
多个线程同时执行到条件判断语句时,会创建多个实例。问题在于当一个线程创建一个实例之后,s就不再为空了,但是后续的线程并没有做第二次非空检查。那么很明显,在同步代码块中应该再次做检查,也就是所谓的双重检测:
public class SingletonDemo01 {
private static SingletonDemo01 s;//声明变量后,不马上去new一个实例。
private SingletonDemo01(){} //私有化构造器
public static SingletonDemo01 getInstance(){//提供一个全局访问方法
if(s==null){
synchronized(SingletonDemo01.class){
if(s==null){
s = new SingletonDemo01();
}
}
}
return s;
}
}
到这里,你也许就会觉得没有问题了,可是问题还是存在的。
在接下来的说明中,我先来解释一下什么叫做有序性、可见性、原子性。
有序性:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
可见性:可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改.
我们都知道,每一个线程都是有自己的工作内存的,线程首先将主内存中的共享变量拷贝到自己的工作内存中,对工作内存中数据进行操作,如果其中一个线程更新了主内存中的变量,而另外的线程没有根据主内存中的变量去更新自己的工作内存,问题出现。这就是不可见性。利用volitile修饰共享变量能后防止此问题。原理是在某线程更改的共享变量后,会通知其他线程及时更新。同时,volitile关键字会阻止指令重排。
(参考博客:链接:Java内存模型与指令重排)
原子性:原子性是指一个操作是不可中断的. 即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰. 例如CPU中的一些指令, 属于原子性的,又或者变量直接赋值操作(i = 1), 也是原子性的, 即使有多个线程对i赋值, 相互也不会干扰.而如i++, 则不是原子性的, 因为他实际上i = i + 1, 若存在多个线程操作i, 结果将不可预期.
指令重排:指令重排就是破坏了有序性。之所以会指令重排。为了性能考虑, 编译器和CPU可能会对指令重新排序.关于指令重排可以参考一下这篇博客(Java并发编程之happens-before - 木易森林 - 博客园)。
我们可以简单的这样理解:我们程序员肯定是想让程序一步一步按照我们编写的顺序执行。就是JMM对我们程序员来说是一种强内存模型。但是编译器和处理器,总是想着如何去提高处理效率,所以他们可能擅自改变程序顺序。这是因为JMM对上(程序员)说:我保证按照你的这个顺序执行,但是他对下(编译器和处理器)说:你们为了提高效率可以改变顺序,但是最终结果你们不能改变。所以JMM对编译器和处理器来说是一种弱内存模型。当然这是在单线程的情况下来说的,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。在多线程下就会产生问题。
上面的代码:
s = new SingletonDemo01();
这不是一个原子性操作:它有如下几个步骤:
memory = allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance = memory; //3:设置instance指向刚分配的内存地址
经过指令重排:
memory = allocate(); //1:分配对象的内存空间
instance = memory; //3:设置instance指向刚分配的内存地址,此时对象还没被初始化
ctorInstance(memory); //2:初始化对象
若有A线程进行完重排后的第二步,且未执行初始化对象。此时B线程来取S时,发现S不为空,于是便返回该值,但由于没有初始化完该对象,此时返回的对象是有问题的。(参考博文:链接:由一个单例模式引发的对指令重排的思考)。
上述代码的改进方法:将S声明为volatile类型即可(volatile有内存屏障的功能)。
当然,我们也可以使用饿汉式.
懒汉式
public class SingletonDemo01 {
private static SingletonDemo01 s = new SingletonDemo02();//声明后马上创建对象
private SingletonDemo02(){} //私有化构造器
public static SingletonDemo02 getInstance(){//提供一个可以返回实例的全局访问点
return s;
}
}
优点:饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。因此,可以省略synchronized关键字。
后续补充:
问题1 、单例模式唯一实例为什么必须为静态?
你只要弄明白单例模式是如何实现的,就能从本质上理解这个问题;
单例模式实现过程如下:
首先,将该类的构造函数私有化(目的是禁止其他程序创建该类的对象);
其次,在本类中自定义一个对象(既然禁止其他程序创建该类的对象,就要自己创建一个供程序使用,否则类就没法用,更不是单例);
最后,提供一个可访问类自定义对象的类成员方法(对外提供该对象的访问方式)。
直白的讲就是,你不能用该类在其他地方创建对象,而是通过该类自身提供的方法访问类中的那个自定义对象。
那么问题的关键来了,程序调用类中方法只有两种方式,①创建类的一个对象,用该对象去调用类中方法;②使用类名直接调用类中方法,格式“类名.方法名()”;
上面说了,构造函数私有化后第一种情况就不能用,只能使用第二种方法。
而使用类名直接调用类中方法,类中方法必须是静态的,而静态方法不能访问非静态成员变量,因此类自定义的实例变量也必须是静态的。
这就是单例模式唯一实例必须设置为静态的原因。
问题2:spring框架为什么将bean设置成单例
前提:如果一个bean被声明为单例的时候,在处理多次请求的时候在Spring容器里只实例化出一个bean,后续的请求都公用这个对象,这个对象会保存在一个map里面。当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象,所以这是个单例的。但是对于原型(prototype)bean来说当每次请求来的时候直接实例化新的bean,没有缓存以及从缓存查的过程。
单例bean的优势
由于不会每次都新创建新对象所以有一下几个性能上的优势:
(1)减少了新生成实例的消耗 新生成实例消耗包括两方面,首先,Spring会通过反射或者cglib来生成bean实例这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法
(2)减少jvm垃圾回收 由于不会给每个请求都新生成bean实例,所以自然回收的对象少了
(3)可以快速获取到bean 因为单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快
单例bean的劣势
单例的bean一个很大的劣势就是他不能做到线程安全!!!,由于所有请求都共享一个bean实例,所以这个bean要是有状态的一个bean的话可能在并发场景下出现问题,而原型的bean则不会有这样问题(但也有例外,比如他被单例bean依赖),因为给每个请求都新创建实例。
简单工厂模式
抽象工厂模式
工厂方法模式
生成器模式
原型模式
结构型模式
适配器模式
桥接模式
组合模式
装饰模式
外观模式
享元模式
代理模式
代理分为静态代理与动态代理
静态代理:有两种实现方式:继承与聚合
继承
public interface Fly {
void fly();
}
public class Bird implements Fly{
@Override
public void fly() {
System.out.println("鸟正在飞过太平洋。。。。。");
}
}
public class BirdSonForSecurity extends Bird{
@Override
public void fly() {
System.out.println("飞行前做安全检查");
super.fly();
System.out.println("飞行后做安全检查");
}
}
public class BirdSonForTime extends Bird {
@Override
public void fly() {
Long start = System.currentTimeMillis();
super.fly();
Long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
public class BirdSonForSecAndTime extends Bird {
@Override
public void fly() {
System.out.println("飞行前做安全检查");
Long start = System.currentTimeMillis();
super.fly();
System.out.println("飞行后做安全检查");
Long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
我们来分析一下:首先有一个接口Fly,一个Bird实现了Fly,我们需要对飞做安全检查的代理,还想做时间记录的代理,还想既要做时间记录和安全检查的代理,顺序还想顺便颠倒,在这样的诉求下,我们需要不断的去创建新的代理类以满足我们的要求,类会很快的膨胀。不过可以通过聚合的方式来进行一定程度的延缓
聚合
public interface Fly {
void fly();
void fastFly();
}
public class Bird implements Fly {
@Override
public void fly() {
System.out.println("鸟正在飞。。。。。");
}
@Override
public void fastFly() {
System.out.println("鸟正在快飞。。。。。");
}
}
public class BirdSonForTime implements Fly{
public BirdSonForTime(Fly fly) {
this.fly = fly;
}
Fly fly;
@Override
public void fly() {
Long start = System.currentTimeMillis();
fly.fly();
Long end = System.currentTimeMillis();
System.out.println(end - start);
}
@Override
public void fastFly() {
Long start = System.currentTimeMillis();
fly.fastFly();
Long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
public class BirdSonForSecurity implements Fly{
public BirdSonForSecurity(Fly fly) {
this.fly = fly;
}
Fly fly;
@Override
public void fly() {
System.out.println("飞行前做安全检查");
fly.fly();
System.out.println("飞行后做安全检查");
}
@Override
public void fastFly() {
System.out.println("飞行前做安全检查");
fly.fastFly();
System.out.println("飞行后做安全检查");
}
}
public class test {
public static void main(String[] args) {
Bird bird = new Bird();
BirdSonForSecurity birdSonForSecurity = new BirdSonForSecurity(bird);
BirdSonForTime birdSonForTime = new BirdSonForTime(birdSonForSecurity);
birdSonForTime.fly();
birdSonForSecurity.fly();
}
}
分析一下:我们可以看到在使用中,我们可以将一个代理类传到另外一个代理类中,从而实现了代理类之间的不同组合,所以延缓的类的膨胀,但是我们可以发现当我们需要对接口中的两个方法做同样的代理时,我们就需要去编写重复的代码。
行为模式
责任链模式
项目实例代码
①.定义过滤器接口,定义了一个filter方法,用于不同的实现类以不同的方式实现。
public interface Filter<T> {
List<T> filter(List<T> list);
}
②.接着定义一个实现类,FilterChain类,这个类比较特殊,他实现了接口方法filter,但是本身这个类并没有进行编写具体过滤规则,而是通过一个for循环调用了一下其他实现类中的filter方法。因此,这个类得持有其他实现类的一个集合引用filters。既然有了集合引用,那就得有增加或者删除其他实现类的方法,这里超哥只写了add方法,并且add方法的返回值是this,这个点很有意思,待会在测试类代码中就会看到他的作用。
public class FilterChain implements Filter<TagViewModel> {
List<Filter> filters = Lists.newArrayList();
public FilterChain add(Filter f) {
filters.add(f);
return this;
}
@Override
public List<TagViewModel> filter(List<TagViewModel> list) {
for (Filter f : filters) {
list = f.filter(list);
}
return list;
}
}
③.接下来就是定义其他具有过滤规则的实现类了,
/**
* Created by huangchao21 on 2019/07/10.
* 过滤掉无效tag。
* 无效标准(满足其一即为无效):
* 1. tagType为null或Empty
* 2. tagContent为null或Empty
* 3. name为null或Empty
*/
public class TagValidFilter implements Filter<TagViewModel>{
@Override
public List<TagViewModel> filter(List<TagViewModel> tags) {
return tags.stream()
.filter(tag -> isValid(tag))
.collect(Collectors.toList());
}
/**
* 判断快筛项是否有效,条件:
* 1. tagType和tagContent必须存在
* 2. 当图片型展示名imgName存在时,文案型展示名name可以不存在;否则文案型展示名name必须存在
* @param tag
* @return
*/
private boolean isValid(TagViewModel tag) {
if (Strings.isNullOrEmpty(tag.getType()) || Strings.isNullOrEmpty(tag.getContent())) {
return false;
}
return !Strings.isNullOrEmpty(tag.getImgName()) || !Strings.isNullOrEmpty(tag.getName());
}
}
/**
* Created by huangchao21 on 2019/07/10.
*/
public class TagNumFilter implements Filter<TagViewModel>{
private static final int MIN_TAB_NUM = 1;
private static final int MAX_TAB_NUM = 10;
@Override
public List<TagViewModel> filter(List<TagViewModel> tags) {
if (tags.size() < MIN_TAB_NUM) {
return Collections.emptyList();
}
if (tags.size() > MAX_TAB_NUM) {
return tags.subList(0, MAX_TAB_NUM);
}
return tags;
}
}
④.接着,我们就看一下具体的调用
tags = new FilterChain()
.add(new TagValidFilter())//将其他实现类对象通过add方法加入FilterChain对象中,返回的是FilterChain对象,
.add(new TagNumFilter())//将其他实现类对象通过add方法加入FilterChain对象中,返回的是FilterChain对象
.filter(tags);//filter方法的方法体内是循环调用其他实现类的具体的过滤规则,并返回过滤结果。
责任链设计模式实现
其实责任链设计模式分为纯的和不纯的。
一个纯的职责链模式要求一个具体处理者对象只能在两个行为中选择一个:要么承担全部责任,要么将责任推给下家,不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递的情况。而且在纯的职责链模式中,要求一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理的情况。
在一个不纯的职责链模式中允许某个请求被一个具体处理者部分处理后再向下传递,或者一个具体处理者处理完某请求后其后继处理者可以继续处理该请求,而且一个请求可以最终不被任何处理者对象所接收。
因此,此时设计的责任链模式,是不纯的责任链设计模式。
注:为了编写方法,具体的过滤规则和测试数据可能不同
①.按照责任链设计模式的原则我们首先定义一个抽象处理者。
package com.meituan.song.java_design_pattern.chain_of_filter;
import java.util.List;
/**
* @ClassName FilterHandler
* @Description TODO
* @Author songyi
* @Date 2020/7/11 3:59 下午
* @Version 1.0
*/
public abstract class FilterHandler<T> {
public String name;
public FilterHandler<T> filterHandler;
public FilterHandler() {
}
public FilterHandler(String name) {
this.name = name;
}
public void setFilterHandler(FilterHandler<T> filterHandler) {
this.filterHandler = filterHandler;
}
public abstract Request processRequest(Request request);
}
②.定义请求类,我将List处理数据,包装了一个外壳
/**
* @ClassName Request
* @Description TODO
* @Author songyi
* @Date 2020/7/11 4:36 下午
* @Version 1.0
*/
public class Request<T> {
public List<T> list;
public Request() {
}
public Request(List<T> list) {
this.list = list;
}
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
}
③.接着定义不同的具体处理实现类
/**
* @ClassName Filter1Handler
* @Description TODO
* @Author songyi
* @Date 2020/7/11 4:07 下午
* @Version 1.0
*/
public class Filter1Handler extends FilterHandler<String>{
public Filter1Handler() {
}
public Filter1Handler(String name) {
super(name);
}
@Override
public Request processRequest(Request request) {//小写转大写
List<String> list = process(request.getList());//本处理器处理过程
request.setList(list);
return this.filterHandler.processRequest(request);//下交给下一任
}
private List<String> process(List<String> list) {
List<String> collect = list.stream()
.map(e -> e.toUpperCase())
.collect(Collectors.toList());
return collect;
}
}
/**
* @ClassName Filter2Handler
* @Description TODO
* @Author songyi
* @Date 2020/7/11 5:03 下午
* @Version 1.0
*/
public class Filter2Handler extends FilterHandler<String>{
public Filter2Handler() {
}
public Filter2Handler(String name) {
super(name);
}
@Override
public Request processRequest(Request request) {//去重复
List<String> list = process(request.getList());
request.setList(list);
return this.filterHandler.processRequest(request);
}
private List<String> process(List<String> list) {
List<String> collect = list.stream()
.distinct()
.collect(Collectors.toList());
return collect;
}
}
/**
* @ClassName Filter3Handler
* @Description TODO
* @Author songyi
* @Date 2020/7/11 5:06 下午
* @Version 1.0
*/
public class Filter3Handler extends FilterHandler<String>{
public Filter3Handler() {
}
public Filter3Handler(String name) {
super(name);
}
@Override
public Request processRequest(Request request) {//取集合前三个
List<String> list = process(request.getList());
request.setList(list);
return request;//责任链的最后一任负责收尾工作。
//this.filterHandler.processRequest(request);
}
private List<String> process(List<String> list){
List<String> collect = list.stream()
.limit(3)
.collect(Collectors.toList());
return collect;
}
}
④.测试
/**
* @ClassName Test
* @Description TODO
* @Author songyi
* @Date 2020/7/11 5:11 下午
* @Version 1.0
*/
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList(6);
list.add("beijing");
list.add("tianjin");
list.add("tianjin");
list.add("jinan");
list.add("shanghai");
list.add("shenzhen");
Request<String> request = new Request<>(list);
FilterHandler<String> filterHandler1 = new Filter1Handler();
FilterHandler<String> filterHandler2 = new Filter2Handler();
FilterHandler<String> filterHandler3 = new Filter3Handler();
filterHandler1.setFilterHandler(filterHandler2);
filterHandler2.setFilterHandler(filterHandler3);
System.out.println("请求处理前:"+request.getList());
Request result = filterHandler1.processRequest(request);
System.out.println("请求处理后:"+result.getList());
}
}
//运行结果
请求处理前:[beijing, tianjin, tianjin, jinan, shanghai, shenzhen]
请求处理后:[BEIJING, TIANJIN, JINAN]
命令模式
迭代器模式
中介者模式
备忘录模式
观察者模式(监听器)
1、概念
当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。当某一个对象(被观察者)的状态发生变化时,被观察者通知所有观察者,观察者做出相应的动作。
2、理解思路
首先定义一个被观察者Subject类,Subject类持有所有观察者的一个集合引用,这是比较重要的,只有被观察者持有观察者的引用,才能在被观察者自己的状态发生改变时,去通知观察者。然后我们定义一个观察者的父类,父类观察者持有被观察者的引用,这个引用是用来做什么的呢?
两个作用:
①.在新的观察者加入时,通过被观察者引用,调用被观察者的add方法,把观察者自己加入到观察者集合中去。
②.其实第二个作用,看场景需求,如果说当被观察者更改自己的状态后,观察者那边需要获取到被观察者这边的一些相关数据,那就可以通过被观察者的引用调用getter方法,获取到状态的改变。如果说,被观察者只是通知观察者进行一些活动,不需要被观察者这边的一些数据,比如只需要观察者输出一句话,那就这个被观察者的引用就没有第二个作用了。
3、代码demo
//被观察者
package com.meituan.song.observerpattern;
import java.util.ArrayList;
public class Subject {
private int state;
private ArrayList<Observer> observers = new ArrayList<>();
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObserver();
}
public void add(Observer observer){
observers.add(observer);
}
private void notifyAllObserver() {
for(Observer observer : observers){
observer.update();
}
}
}
//观察者父类
package com.meituan.song.observerpattern;
public abstract class Observer {
protected Subject subject ;
abstract void update();
}
//观察者1
package com.meituan.song.observerpattern;
public class Observer1 extends Observer{
public Observer1(Subject subject){
this.subject = subject;
this.subject.add(this);//这里可以单独讲add方法拿出来,做成一个方法,在方法体中
} //通过subject引用去调用subject方法体中的add方法。
@Override //另外还可以增加delete方法。
void update() {
System.out.println("我是观察者1");
System.out.println(subject.getState());
}
}
//观察者2
package com.meituan.song.observerpattern;
public class Observer2 extends Observer{
public Observer2(Subject subject){
this.subject = subject;
this.subject.add(this);
}
@Override
void update() {
System.out.println("我是观察者2");
System.out.println(subject.getState());
}
}
//测试类
package com.meituan.song.observerpattern;
public class Test {
public static void main(String[] args) {
Subject subject = new Subject();
new Observer1(subject);
new Observer2(subject);
subject.setState(1);
subject.setState(2);
}
}
4、代码逻辑
首先创建一个被观察者,接着创建两个观察者,同时将被观察者传入,在观察者的构造方法中,不仅是对观察者中的subject赋值,同时,调用subject的的add方法把自己(观察者)的引用传到subject对象中,这就是我说第一个作用。接着我们试着改变了被观察者中的state的值,就是改变了被观察者的状态,那我们可以看出,在set方法体中,一旦state的值发生了变化,就会调用一个notifyAll方法去循环通知所有的观察者。接着在notifyAll的方法体中,去循环调用所有观察者的update方法,之所有能够调用,是因为被观察者持有所有观察者集合的引用。接着,在每一个观察者的具体的update方法中,自己爱怎么实现就怎么实现就可以了,如果需要获取被观察者状态改变的数据,就通过被观察者的引用来调getter方法即可,这就是我说的第二个作用,如果不需要,执行别的操作也可以了。方法调用逻辑以及类图如图所示: