剑指架构师系列-设计模式

author author     2022-09-18     344

关键词:

 

1、单例模式:

确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式有以下几个要素:

  • 私有的构造方法
  • 指向自己实例的私有静态引用
  • 以自己实例为返回值的静态的公有的方法

        单例模式根据实例化对象时机的不同分为两种:一种是饿汉式单例,一种是懒汉式单例。饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用;而懒汉式在调用取得实例方法的时候才会实例化对象。

饿汉式:

public class Singleton_Simple {  
      
    private static final Singleton_Simple simple = new Singleton_Simple();  
      
    private Singleton_Simple(){}  
      
    public static Singleton_Simple getInstance(){  
        return simple;  
    }  
  
}

懒汉式:

//双锁机制
class Singleton {
	private volatile static Singleton singleton;
	public static Singleton getInstance() {
		if (singleton == null) {
			synchronized (Singleton.class) { // 由于每次调用都需要进行同步,所以可以在前面进行判断即可提高效率,同时注意使用的是Singleton.class的类锁
				if (singleton == null) {
					singleton = new Singleton();
				}
			}
		}
		return singleton;
	}
}

为了提高效率。我们可以用double check机制,现在来看两个问题:

(1)为何用double check的检测机制?

由于两个线程都可以通过第一重的判断 ,进入后,由于存在锁机制,所以会有一个线程进入同步块和第二重判断,而另外的一个线程在同步块处阻塞。

当第一个线程退出同步代码块后,第二个进入,没有经过第二重判断,保证了单例。所以这里必须要使用双重检查锁定。

其实没有第一重的判断,我们也可以实现单例,只是为了考虑性能问题。

(2)为何要对实例变量加volatile关键字?

java内存模型(jmm)并不限制处理器重排序,在执行instance=new Singleton()时,并不是原子语句,实际是包括了下面三大步骤: 

1.为对象分配内存

2.初始化实例对象

3.把引用instance指向分配的内存空间

ps:对象创建可参看《深入理解Java虚拟机》第44页

这个三个步骤并不能保证按序执行,处理器会进行指令重排序优化,存在这样的情况:

优化重排后执行顺序为:1,3,2, 这样在线程1执行到3时,instance已经不为null了,线程2此时判断instance!=null,则直接返回instance引用,但现在实例对象还没有初始化完毕,此时线程2使用instance可能会造成程序崩溃。

现在要解决的问题就是怎样限制处理器进行指令优化重排。

volatile的作用:

(1)volatile变量不会以被缓存到寄存器或者对其它处理器不可见的地方,因此在读取volatile类型的变量时总是返回最新写入的值。 

(2)volatile关键字能够通过提供内存屏障,来保证某些指令顺序处理器不能够优化重排,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

 

JVM会对分配内存空间的动作进行同步处理。

(1)可能实际上采用CAS(Compare And Set)配上失败重试的方式保证更新操作的原子性

(2)把内存分配的动作按照线程划分在不同的空间之中进行。就是每个线程在Java堆中预先分配一小块内存,

参考Java虚拟机第45页

 

其实还有种写法,如下:

public class Singleton { 
    
	  private static class SingletonClassInstance { // 私有静态内部类
	    // 可能是final解决了并发的问题,基本类型声明就可,但是对象类型时,这个对象不能有状态,
	    // 如果有状态,则一定要声明为final,例如String类就是一个不可变类
	    private static final Singleton instance = new Singleton(); 
	  } 

	  public static Singleton getInstance() { 
	    return SingletonClassInstance.instance; // 只在这里调用了静态内部类,私有属性让他人无法使用这个类型
	  } 

	  private Singleton() {  } 
}

由于Singletom没有static属性且SingletonClassInstance是私有静态内部类,不会被其他类调用,所以不会被提前初始化,实现了懒汉式的构造实例。

static语义也要求不会有多个实例存在。并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。
  
虚拟机会保证一个类的<clinit>()方法在多线程环境中被 正确地加锁、同步。如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。

 

重点要知道为什么静态内部类能保证单例:因为只有一个线程去初始化这个类,在初始化后static final就是一个常量了,当然线程安全了。 

 

其在实际中有重要的应用,如:

1、单例模式在Log4j中的应用。将日志输出到一个文件中必须使用单例模式,否则会覆盖文件的原有内容
2、数据库连接池中的应用

 

2、迭代器模式:

提供一种方法访问一个容器对象中各个元素,而又不暴露该对象的内部细节

interface Iterable{
	public Iterator iterator();
}

interface Aggregate extends Iterable{
	public void add(Object obj);
	public void remove(Object obj);
	public Iterator iterator();
}

class ConcreteAggregate implements Aggregate {
	private List list = new ArrayList();

	public void add(Object obj) {
		list.add(obj);
	}

	public Iterator iterator() {
		return new ConcreteIterator(list);
	}

	public void remove(Object obj) {
		list.remove(obj);
	}
}

调用iterator()方法后返回一个Iterator迭代器,实现如下:

interface Iterator {
	public Object next();
	public boolean hasNext();
}

class ConcreteIterator implements Iterator {
	private List list = new ArrayList();
	private int cursor = 0;

	public ConcreteIterator(List list) {
		this.list = list;
	}

	public boolean hasNext() {
		if (cursor == list.size()) {
			return false;
		}
		return true;
	}

	public Object next() {
		Object obj = null;
		if (this.hasNext()) {
			obj = this.list.get(cursor++);
		}
		return obj;
	}
}

测试一下:

Aggregate ag = new ConcreteAggregate();
ag.add("小明");
ag.add("小红");
ag.add("小刚");
Iterator it = ag.iterator();
while (it.hasNext()) {
	String str = (String) it.next();
	System.out.println(str);
}

 

3、组合设计模式:

蜜蜂是昆虫:继承实现

蜜蜂有向前移动后攻击人的行为:组合实现

昆虫:

class Insect {
    private String name;
    public Insect(String name) {
        this.name = name;
    }
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

攻击行为:

interface Attack {
    public void move();
    public void attack();
}

class AttackImpl implements Attack {
    private String move;
    private String attack;
 
    public Attack Impl(String move, String attack) {
        this.move = move;
        this.attack = attack;
    }
    @Override
    public void move() {
        System.out.println(move);
    }
    @Override
    public void attack() {
        move();
        System.out.println(attack);
    }
}

蜜蜂是昆虫,有向前一步攻击人的属性:

class Bee extends Insect implements Attack {
    private Attack attack;
 
    public Bee(String name, Attack attack) {
        super(name);
        this.attack = attack;
    }
 
    public void move() {
        attack.move();
    }
 
    public void attack() {
        attack.attack();
    }
}

由于继承机制太过依赖于父类的实现细节,如果父类变动,则子类也会跟着变动,这是糟糕的。 

 

4、策略设计模式:

// Different types of function objects:
interface Combiner<T> {
	T combine(T x, T y);
}


public class Functional {
	// Calls the Combiner object on each element to combine
	// it with a running result, which is finally returned:
	public static <T> T reduce(Iterable<T> seq, Combiner<T> combiner) {
		Iterator<T> it = seq.iterator();
		if (it.hasNext()) {
			T result = it.next();
			while (it.hasNext()){
				result = combiner.combine(result, it.next());
			}
			return result;
		}
		// If seq is the empty list:
		return null; // Or throw exception
	}


	// To use the above generic methods, we need to create
	// function objects to adapt to our particular needs:
	static class IntegerAdder implements Combiner<Integer> {
		public Integer combine(Integer x, Integer y) {
			return x + y;
		}
	}

	static class IntegerSubtracter implements Combiner<Integer> {
		public Integer combine(Integer x, Integer y) {
			return x - y;
		}
	}

	static class BigIntegerAdder implements Combiner<BigInteger> {
		public BigInteger combine(BigInteger x, BigInteger y) {
			return x.add(y);
		}
	}


	public static void main(String[] args) {
		
		// Generics, varargs & boxing working together:
		List<Integer> li = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
		
		Integer result = reduce(li, new IntegerAdder());
		print(result); // 28

		result = reduce(li, new IntegerSubtracter());
		print(result);


		// Use the prime-generation facility of BigInteger:
		List<BigInteger> lbi = new ArrayList<BigInteger>();
		BigInteger bi = BigInteger.valueOf(11);
		for (int i = 0; i < 11; i++) {
			lbi.add(bi);
			bi = bi.nextProbablePrime();
		}
		print(lbi);

		BigInteger rbi = reduce(lbi, new BigIntegerAdder());
		print(rbi);
		
	}
}

如上例子参考了Java编程思想关于泛型实现策略模式的例子。  

 

下面来总结一下各个设计模式在Java中的具体应用。

结构型模式

 

单例模式(Single Pattern)

1、Log4j中将日志输出到一个文件中必须使用单例模式,否则会覆盖文件的原有内容

2、数据库连接池中的应用

工厂模式(Factory)

Spring工厂模式:通过XML文件或Java注解来表示Bean之间的依赖关系,很少直接new一个类进行代码编写

建造者模式(Builder)

Struts2中创建容器(Container)对象

原型模式(Protype)

Java的克隆clone()

 结构型模式

 

适配器模式(Adapter)

1、在Java的I/O类库中,StringReader将一个String类适配到Reader接口,InputStreamReader将InputStream适配到Reader类。

2、在Spring中的AOP中,由于Advisor需要的是MethodInterceptor对象,所以每一个Advisor中的Advice都要配成对应的MethodInterceptor对象。

桥接模式(Bridge)

 

装饰模式(Decorator)

1、Collections.synchronizedList例子也是一个装饰器模式

2、装饰器在Junit中的应用。TestDecorator是Test的装饰类,其TestDecorator有一些扩展功能的子类。如RepeatedTest类,TestSetup类等

3、一般情况下,需要使用FileReader和StringReader,如果要增加缓存功能的类有很多,那么子类也就需要很多,所以Java就使用了装饰模式,BufferedReader就是这样的装饰类。其实Java I/O 库中的所有输入流、输出流的类都采用了装饰器模式,它们可以无限次地进行装饰转换,转换的目的就是得到自己想要的数据类型的流对象

组合模式(Composite)

 

外观模式(Fa?ade)

1、在Spring中已经提供了很好的封装,在org.springframework.jdbc.support包下提供的JdbcUtils类就是这样的工具类

2、在Hibernate的配置工作中,有一个Configuration类,负责管理运行时需要的一些信息

享元模式(Flyweight)

数据据连接池是享元模式的重要应用。在Java中,对于基本类型和字符串类型的实现就采用了享元模式

代理模式(Proxy)

1、在Spring中已经提供了很好的封装,在org.springframework.jdbc.support包下提供的JdbcUtils类就是这样的工具类

2、在Hibernate的配置工作中,有一个Configuration类,负责管理运行时需要的一些信息

行为型模式

 

模版方法模式(Template Method)

1、在Junit中的TestCase类中

2、在MVC框架的HttpServlet中

3、Spring采用开放式的处理方式,在使用JpaTemplate时,只需要处理具体的SQL语句即可,而创建连接、关闭连接等方法都不需要编写代码,因为不管理是哪种持久层的操作应运,其创建和关闭连接都是一致的,按照相同的顺序执行,这就是模板方法模式的应用

命令模式(Command )

 

命令模式在Struts2中的应用。其中action就是命令模式中命令接口,ActionInvocation就是命令模式中命令的调用者

迭代器模式(Iterator )

java中的Collection,List、Set、Map等,这些集合都有自己的迭代器

观察者模式(Oberver Pattern)

 

中介者模式(Mediator)

 

备忘录模式(Memento)

 

解释器模式(Interpreter)

 

状态模式(State)

 

职责链模式(Chain of Responsibility)

责任链在Struts2中的拦截器上有重要应用。



策略模式(Strategy)

1、策略模式允许在程序执行时选择不同的算法.比如在排序时,传入不同的比较器(Comparator),就采用不同的算法

2、Spring的Resource实现思想是典型的策略模式的应用

访问者模式(Visitor)

 

  

 

 

 

 

 

 

 

 

剑指架构师系列-struts2的缓存

 Struts2的缓存中最重要的两个类就是ReferenceMap与ReferenceCache。下面来解释下ReferenceCache中的get()方法。publicVget(finalObjectkey){ Vvalue=super.get(key); return(value==null)?internalCreate((K)key):value;}通过key来获取value 查看详情

剑指架构师系列-springboot的logback日志记录

  SpringBoot集成了Logback日志系统。Logback的核心对象主要有3个:Logger、Appender、Layout 1、Logback Logger:日志的记录器 主要用于存放日志对象,也可以定义日志类型、级别。级别:ERROR、WARE、INFO、DEBUG和TRACE。没有FATA... 查看详情

剑指架构师系列-hibernate需要掌握的annotation

 1、一对多的关系配置@Entity@Table(name="t_order")publicclassOrder{ @Id @GeneratedValue privateintid; privateStringname; /**该属性定义类和类之间的级联关系。定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操作,*而且这种... 查看详情

剑指架构师系列-struts2构造函数的循环依赖注入

 Struts2可以完成构造函数的循环依赖注入,来看看Struts2的大师们是怎么做到的吧!首先定义IBlood与BloodImpl类: publicinterfaceIBlood{}publicclassBloodImplimplementsIBlood{ privateIPeoplepeople; @Inject publicBloodImpl(@InjectIPeop 查看详情

剑指架构师系列-logstash分布式系统的日志监控

 Logstash主要做由三部署组成:Collect:数据输入Enrich:数据加工,如过滤,改写等Transport:数据输出下面来安装一下:wgethttps://download.elastic.co/logstash/logstash/logstash-2.3.2.tar.gztar-zxvflogstash-2.3.2.tar.gz  在logstash-2.3.2目录下创建文 查看详情

剑指架构师系列-innodb存储引擎spring事务与缓存

 事务与锁是不同的。事务具有ACID属性:原子性:持久性:由redolog重做日志来保证事务的原子性和持久性,一致性:undolog用来保证事务的一致性隔离性:一个事务在操作过程中看到了其他事务的结果,如幻读。锁是用于解决隔离... 查看详情

剑指架构师系列-持续集成之maven+nexus+jenkins+git+springboot

 1、Nexus与Maven 先说一下这个Maven是什么呢?大家都知道,Java社区发展的非常强大,封装各种功能的Jar包满天飞,那么如何才能方便的引入我们项目,为我所用呢?答案就是Maven,只需要粘贴个Jar包的地址,Maven就会自动到... 查看详情

架构师之路系列文章

...字化转型架构师之路业务架构应用架构API经济数据库设计设计模式系统架构分布式系统RPC远程调用分布式消息队列分布式任务队列微服务架构ServiceComb部署架构高可靠、高可用、高并发、高性能、高可扩展集群高性能要求运维架... 查看详情

成为架构师课程系列怎样进行概念架构(conceptualarchitecture)?

目录 前言什么是概念架构概念架构阶段的3个步骤 初步设计高层分割分层式概念服 查看详情

成为架构师课程系列架构设计中的核心思维方法

前言架构的本质是管理复杂性。抽象、分层、分治和演化思维是我们工程师/架构师应对和管理复杂性的四种最基本武器。最近团队来了一些新人,有些有一定工作经验,是以高级工程师/架构师身份进来的,但我发现他们大部分... 查看详情

架构师必知必会系列系统架构设计需要知道的5大精要(5systemdesignfundamentals)...(代码片段)

无论是在大厂还是初创公司,技术产品经理(TPM)都需要具备系统设计的基础知识。从历史上看,系统设计基础知识通常是软件工程师在面试时的要求,而TPM不受此期望的约束。然而,现在趋势正在发生变化。作为TP... 查看详情

成为架构师课程系列系统架构设计:非功能性目标的设计(代码片段)

...天类比汽车行业,后台类比拍电影。我并不能确定把架构师和哪个职业相类比最适合,但或许,架构师最嫉妒的职业是拳击。人家的目标永远正确:打到对方。而架构师,却要面对纠结在一起的“需求”--需求... 查看详情

架构整洁之道系列软件架构师与软件架构

...在读《CleanArchitecture》这本书,书中对与软件设计与架构的阐述是非常深刻的。因此开了一篇专栏,来记录《CleanArchitecture》书中一些优秀的架构设计理念,以及我对这些内容的思考。一、什么是软件架构师软件架构... 查看详情

成为架构师课程系列架构师的核心能力地图

目录架构师核心能力总结#综合技术能力分层总结#数据结构和算法知识图谱总结#Java工程师【核心基础】知识图谱总结 查看详情

成为架构师课程系列一线架构师:6个经典困惑及其解法(代码片段)

 目录 一线架构师:6个经典困惑及其解法 多阶段还是多视图?内置最佳实践架构方法论:3个阶段,一个贯穿 Pre-architecture阶段:ADMEMS矩阵方法ConceptualArchitecture阶段:重大需求塑造做概念架构RefinedArchitecture阶段:落... 查看详情

java进阶架构师指南之一:如何进行架构设计

...验,java基础扎实,想突破自己的技术瓶颈,成为一位优秀的架构师,所谓java基础扎实,比如:??1.java语言三大特性.??2.java语言八大基本类型及其表示范围.??3.为什么float和double存在精度丢失???4.publish/priva 查看详情

成为架构师课程系列高并发系统设计的三大目标:高性能高可用可扩展

...也就是 高并发、高性能、高可用,它们是互联网系统架构设计永恒的主题。在前两节课中,我带你了解了高并发系统设计的含义,意义以及分层设计原则,接下来,我想带你整体了解一下高并发系统设计的目标,然后在此基... 查看详情

成为架构师课程系列使用cache-aside模式将数据存储在缓存中(usingthecache-asidepatterntostoredatainthecache)

目录 前言背景和问题解决方案问题和注意事项何时使用此模式例子前言按需将数据从数据存储加载到缓存中(Cache-Aside )。这种模式可以提高性能,还有助于保持缓存中保存的数据与底层数据存储中的数据之间的一致性... 查看详情