技术知识点总结

Spring 相关   

Spring 事物失效问题

 

事物@Transaction是基于AOP实现的功能增强  内部调用时并不是调用增强后的代理类 而是原始类

 

Spring bean生命周期

实例化 属性赋值  初始化  销毁

在实例化前后调用InstantiationAwareBeanPostProcessor  

初始化前后调用BeanPostProcessor

初始化在调用BeanPostProcessor after方法之前调用init method

在销毁之前调用 destory方法

 

Spring IOC AOP实现原理

基于java动态代理  接口类   非接口类基于CGlib动态代理 

两者区别  jdk动态代理基于接口实现InvocationHandler接口 使用反射

CGlib 对类进行代理 直接调用目标类

 

 

Spring 循环依赖

set方法的循环依赖可以根据提前预加载在三级缓存中   未初始化就加在进去

1.在字段上使用@Autowired注解,让Spring决定在合适的时机注入

2.用基于setter方法的依赖注入

 

Java   

集合  

List Set Map

List Set 继承Collection  Map

详细可参考:https://blog.csdn.net/feiyanaffection/article/details/81394745

HashMap源码  负载因子0.75  默认容量16

put过程 

hash算法

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

先将Key转换为hash值,计算hashcode值,以及数组的长度,确定出该key所处的下标值,for循环遍历Entry数组。首先会判断这个数组里面所有的元素是的hash和key的地址是否一样,如果hash和key地址一样,那么再去判断key值是否一样,如果key值也一样,则会覆盖原先key的value  不一样的话 添加到链表的(jdk1.8之前是头插法  1.8之后尾插法)
 

扩容过程 

 

    当达到默认初始化容量16*负载因子0.75 也就是12之后会进行扩容  扩容后容量会变成原来2倍也就是32

    HashMap是先插入还是先扩容:HashMap是先插入数据再进行扩容的。

Hashmap为什么大小是2的幂次

因为在计算元素该存放的位置的时候,用到的算法是将元素的hashcode与当前map长度-1进行与运算

static int indexFor(int h, int length) {
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}

 

ConcurrentHashMap 

jdk 1.7使用的Segment是ReentrantLock子类,因此拥有锁的操作

jdk1.8 

put方法并没有加锁,那么它是如何保证创建新表的时候并发安全呢?答案就是这里的 sizeCtl ,sizeCtl 默认值为0,当一个线程初始化数组时,会将 sizeCtl 改成 -1,由于被 volatile 修饰,对于其他线程来说这个变化是可见的,上面代码看到后续线程判断 sizeCtl 小于0 就会让出执行权。

Java8 摒弃了Segment,而是对数组中单个位置加锁。当指定位置节点不为 null 时,情况与 Java8 HashMap 操作类似,新节点的添加还是尾部插入方式。

 

线程池源码

 

  • 工作线程不会死(不设置线程存活时间,默认情况下),会一直拿任务,所以工作线程会一直活着
  • 工作线程拿任务的时候,默认情况下,因为用的是 BlockingQueue 的 take() 拿不到任务会阻塞

工作线程 设置成了非守护线程  因此会一直存活setDaemon(false);

参数 需要掌握

亲缘性线程池实现  KeyAffinityExecutor 原理  线程池中套了多个单线程的线程池。

juc包

1.volatile 关键字保证内存可见性;

2.CAS(Compare-And-Swap) 算法 保证数据的原子性;

3.AQS队列   (Unsafe类的park操作是调用Posix的信号量互斥量 condition)

 

atomic类  信号量  countdownlatch 循环栅栏 

ThreadLocal

作用:它是线程的局部变量,这些变量只能在这个线程内被读写,在其他线程内是无法访问的。 ThreadLocal 定义的通常是与线程关联的私有静态字段

使用场景:就是当我们只想在本身的线程内使用的变量,可以用 ThreadLocal 来实现,并且这些变量是和线程的生命周期密切相关的,线程结束,变量也就销毁了。

高性能序列化框架 Kyro 就要用 ThreadLocal 来保证高性能和线程安全;还有像线程内上线文管理器、数据库连接等可以用到 ThreadLocal;

实现原理:

首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。

因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个 ThreadLocalMap 类对应的 get()、set() 方法。

问题  内存泄漏  未调用显示的remove方法可能会造成内存泄漏

所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap 中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。

 

死锁产生的条件 

(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不可剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 环路等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

不可变类的实现

 

1)类声明为final,不可以被继承
2)所有成员是私有的,不允许直接被访问
3)对变量不要setter方法
4)所有可变的变量是final的,只能赋值一次
5)通过构造器初始化所有成员,进行深拷贝
6)在getter方法中不能返回对象本身,返回对象的拷贝

 

java中的锁

从线程是否需要对资源加锁可以分为 悲观锁 和 乐观锁

从资源已被锁定,线程是否阻塞可以分为 自旋锁

从多个线程并发访问资源,也就是 Synchronized 可以分为 无锁、偏向锁、 轻量级锁 和 重量级锁

从锁的公平性进行区分,可以分为公平锁 和 非公平锁

从根据锁是否重复获取可以分为 可重入锁 和 不可重入锁

从那个多个线程能否获取同一把锁分为 共享锁 和 排他锁

 

volatile synchronize

https://www.cnblogs.com/jyroy/p/11365935.html

AQS 

https://www.cnblogs.com/hongmoshui/p/10627277.html

java IO  BIO NIO AIO

 

jvm 

 

-Xss 32位机器最小值是108k  但是设置100k时也能启动    64位最小值是160k

线程私有   负责Java程序的运行,它是在线程创建时创建的,所以生命周期也是和线程生命周期一致,同时消亡,线程结束了栈也就释放,特别提醒的是栈不存在垃圾回收的问题,因为线程结束栈就是释放了。平时我们写的类变量、引用类型变量、实例方法等等都是在函数的栈内存分配好。

线程私有,生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)

本地方法栈

线程私有   native方法 主要作用是登记native方法,然后在execution engine执行的时候加载本地方法库。

程序计数器

线程私有   PC中存放的是下一步要访问的内存地址。

线程共享(TLAB Thread Local Allocation Buffer除外) 垃圾回收

堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间From Survivor空间To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;

方法区

存储了每个类的信息(包括类的名称、修饰符、方法信息、字段信息)、类中静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息以及编译器编译后的代码等。

方法区中有一个非常重要的部分就是运行时常量池,用于存放静态编译产生的字面量和符号引用。运行时生成的常量也会存在这个常量池中,比如String的intern方法。它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。

jmm

java内存模型

  • 多CPU:一个现代计算机通常由两个或者多个CPU。其中一些CPU还有多核。从这一点可以看出,在一个有两个或者多个CPU的现代计算机上同时运行多个线程是可能的。每个CPU在某一时刻运行一个线程是没有问题的。这意味着,如果你的Java程序是多线程的,在你的Java程序中每个CPU上一个线程可能同时(并发)执行。
  • CPU寄存器:每个CPU都包含一系列的寄存器,它们是CPU内内存的基础。CPU在寄存器上执行操作的速度远大于在主存上执行的速度。这是因为CPU访问寄存器的速度远大于主存。
  • 高速缓存cache:由于计算机的存储设备与处理器的运算速度之间有着几个数量级的差距,所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓冲:将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了。CPU访问缓存层的速度快于访问主存的速度,但通常比访问内部寄存器的速度还要慢一点。每个CPU可能有一个CPU缓存层,一些CPU还有多层缓存。在某一时刻,一个或者多个缓存行(cache lines)可能被读到缓存,一个或者多个缓存行可能再被刷新回主存。
  • 内存:一个计算机还包含一个主存。所有的CPU都可以访问主存。主存通常比CPU中的缓存大得多。
  • 运作原理:通常情况下,当一个CPU需要读取主存时,它会将主存的部分读到CPU缓存中。它甚至可能将缓存中的部分内容读到它的内部寄存器中,然后在寄存器中执行操作。当CPU需要将结果写回到主存中去时,它会将内部寄存器的值刷新到缓存中,然后在某个时间点将值刷新回主存。

缓存一致性问题:在多处理器系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存(MainMemory)。基于高速缓存的存储交互很好地解决了处理器与内存的速度矛盾,但是也引入了新的问题:缓存一致性(CacheCoherence)。当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致的情况,如果真的发生这种情况,那同步回到主内存时以谁的缓存数据为准呢?为了解决一致性的问题,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作,这类协议有MSI、MESI(IllinoisProtocol)、MOSI、Synapse、Firefly及DragonProtocol,

  • 线程之间的共享变量存储在主内存(Main Memory)中
  • 每个线程都有一个私有的本地内存(Local Memory),本地内存是JMM的一个抽象概念,并不真实存在,它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化。本地内存中存储了该线程以读/写共享变量的拷贝副本。
  • 从更低的层次来说,主内存就是硬件的内存,而为了获取更好的运行速度,虚拟机及硬件系统可能会让工作内存优先存储于寄存器和高速缓存中。
  • Java内存模型中的线程的工作内存(working memory)是cpu的寄存器和高速缓存的抽象描述。而JVM的静态内存储模型(JVM内存模型)只是一种对内存的物理划分而已,它只局限在内存,而且只局限在JVM的内存。

 

解决这个内存可见性问题你可以使用:

  • Java中的volatile关键字:volatile关键字可以保证直接从主存中读取一个变量,如果这个变量被修改后,总是会被写回到主存中去。Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的,无论是普通变量还是volatile变量都是如此,普通变量与volatile变量的区别是:volatile的特殊规则保证了新值能立即同步到主内存,以及每个线程在每次使用volatile变量前都立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。
  • Java中的synchronized关键字:同步快的可见性是由“如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值”、“对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store和write操作)”这两条规则获得的。
  • Java中的final关键字:final关键字的可见性是指,被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那么在其他线程就能看见final字段的值(无须同步)

类加载机制 

双亲委派

加载--链接--初始化--使用---卸载

        (1) 通过一个类的全限定名(包名与类名)来获取定义此类的二进制字节流

        (2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

        (3) 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。

 

编译优化 

,即时编译器产生的本地代码会比Javac产生的字节码更加优秀

   方法内联、冗余访问消除、覆写传播、无用代码消除。公共子表达式消除 数组边界检查消除 方法内联  逃逸分析

分析对象动态作用域,当一个方法被定以后,它可能被外部方法所引用,称为方法逃逸,甚至还有可能被外部线程访问到,称为线程逃逸。

   若能证明一个对象不会逃逸到方法或线程之外,这可以通过栈上分配、同步消除、标量替换来进行优化。在一般应用中不会逃逸的局部对象所占比例很大,若能栈上分配就会随着方法的结束而自动销毁了,垃圾回收系统的压力将会小很多。如果一个数据可以继续分解,则称它为聚合量。对于一个对象,将其使用到的成员变量恢复原始类型来访问就叫做标量替换。
 

中间件 

rpc  mq   es(倒排索引)   kafka

RPC

1:方法调用方式不同:

  RMI中是通过在客户端的Stub对象作为远程接口进行远程方法的调用。每个远程方法都具有方法签名。如果一个方法在服务器上执行,但是没有相匹配的签名被添加到这个远程接口(stub)上,那么这个新方法就不能被RMI客户方所调用。

  RPC中是通过网络服务协议向远程主机发送请求,请求包含了一个参数集和一个文本值,通常形成“classname.methodname(参数集)”的形式。RPC远程主机就去搜索与之相匹配的类和方法,找到后就执行方法并把结果编码,通过网络协议发回。

 2:适用语言范围不同:

  RMI只用于Java;

  RPC是网络服务协议,与操作系统和语言无关。

 3:调用结果的返回形式不同:

  Java是面向对象的,所以RMI的调用结果可以是对象类型或者基本数据类型;

  RMI的结果统一由外部数据表示 (External Data Representation, XDR) 语言表示,这种语言抽象了字节序类和数据类型结构之间的差异。
 

现实中不直接使用RMI作为远程调用的原因:

  1. 底层是BIO、而不是NIO
  2. 其只能用于Java,不能扩展其他语言
  3. 底层序列化使用JDK的序列化机制,效率不高  

 

JMQ在服务端存储设计上与KAFKA有一些相似的地方,借鉴了文件按照偏移位置管理、顺序追加等特点。不过JMQ的存储和消费模型有自己的特点:

消息存放

JMQ每个存储系统只有一个分段存储的日志文件,不同类的消息按照服务端接收的顺序存放在日志文件中,通过索引程序按照不同的消息(主题)分类名异步创建各自的索引,方便消费端获取消息时快速定位该客户端所关心的(主题)分类消息。每个(主题)分类的索引划分了多个分区,同一(主题)分类的消息分配在多组服务器上的分区数是相同的。每个索引分区都是以链表按照时间序存放消息引用信息。

消费

JMQ也采用客户端主动拉取的方式,但是客户端不需要协调自己应该从哪个服务器上获取消息,服务端会控制好每个索引分区里对应的消息在同一时刻只会被一个客户端线程取走,直到客户端反馈消费成功或者消费异常,消费异常会被重试程序转移到重试服务中。如果客户端长时间没有反馈信息,达到了超时时间,那么锁定的消息可以被其他的线程拉取走。

由于服务端储存了每个消费者消费的位置,因此服务器可以随时把已经消费的消息移除走。

 

ES

 

文档(Document):一般搜索引擎的处理对象是互联网网页,而文档这个概念要更宽泛些,代表以文本形式存在的存储对象,相比网页来说,涵盖更多种形式,比如Word,PDF,html,XML等不同格式的文件都可以称之为文档。再比如一封邮件,一条短信,一条微博也可以称之为文档。在本书后续内容,很多情况下会使用文档来表征文本信息。

       文档集合(Document Collection):由若干文档构成的集合称之为文档集合。比如海量的互联网网页或者说大量的电子邮件都是文档集合的具体例子。

       文档编号(Document ID):在搜索引擎内部,会将文档集合内每个文档赋予一个唯一的内部编号,以此编号来作为这个文档的唯一标识,这样方便内部处理,每个文档的内部编号即称之为“文档编号”,后文有时会用DocID来便捷地代表文档编号。

       单词编号(Word ID):与文档编号类似,搜索引擎内部以唯一的编号来表征某个单词,单词编号可以作为某个单词的唯一表征。

       倒排索引(Inverted Index):倒排索引是实现“单词-文档矩阵”的一种具体存储形式,通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。倒排索引主要由两个部分组成:“单词词典”和“倒排文件”。

       单词词典(Lexicon):搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。

       倒排列表(PostingList):倒排列表记载了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项(Posting)。根据倒排列表,即可获知哪些文档包含某个单词。

       倒排文件(Inverted File):所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件即被称之为倒排文件,倒排文件是存储倒排索引的物理文件。

Hbase Cansandra Spark(引申语言scala)

redis集群

主从模式的缺点其实从上面的描述中可以得出:

master节点挂了以后,redis就不能对外提供写服务了,因为剩下的slave不能成为master
  这个缺点影响是很大的,尤其是对生产环境来说,是一刻都不能停止服务的,所以一般的生产坏境是不会单单只有主从模式的。所以有了下面的sentinel模式。

当使用sentinel模式的时候,客户端就不要直接连接Redis,而是连接sentinel的ip和port,由sentinel来提供具体的可提供服务的Redis实现,这样当master节点挂掉以后,sentinel就会感知并将新的master节点提供给使用者。

sentinel模式基本可以满足一般生产的需求,具备高可用性。但是当数据量过大到一台服务器存放不下的情况时,主从模式或sentinel模式就不能满足需求了,这个时候需要对存储的数据进行分片,将数据存储到多个Redis实例中,就是下面要讲的。

cluster的出现是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器。对cluster的一些理解:

cluster可以说是sentinel和主从模式的结合体,通过cluster可以实现主从和master重选功能,所以如果配置两个副本三个分片的话,就需要六个Redis实例。
因为Redis的数据是根据一定规则分配到cluster的不同机器的,当数据量过大时,可以新增机器进行扩容
  这种模式适合数据量巨大的缓存要求,当数据量不是很大使用sentinel即可。

 

Redis持久化RDB和AOF

  1. Redis 默认开启RDB持久化方式,在指定的时间间隔内,执行指定次数的写操作,则将内存中的数据写入到磁盘中。
  2. RDB 持久化适合大规模的数据恢复但它的数据一致性和完整性较差。
  3. Redis 需要手动开启AOF持久化方式,默认是每秒将写操作日志追加到AOF文件中。
  4. AOF 的数据完整性比RDB高,但记录内容多了,会影响数据恢复的效率。
  5. Redis 针对 AOF文件大的问题,提供重写的瘦身机制。
  6. 若只打算用Redis 做缓存,可以关闭持久化。
  7. 若打算使用Redis 的持久化。建议RDB和AOF都开启。其实RDB更适合做数据的备份,留一后手。AOF出问题了,还有RDB。

设计模式 

单一职责原则  

开闭原则  一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭

里氏替换原则  所有引用基类的地方必须能透明地使用其子类的对象

迪米特法则  如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

接口隔离原则 1、客户端不应该依赖它不需要的接口。
2、类间的依赖关系应该建立在最小的接口上

依赖倒置原则 1、上层模块不应该依赖底层模块,它们都应该依赖于抽象。
2、抽象不应该依赖于细节,细节应该依赖于抽象。

抽象工厂模式(Abstract Factory) 提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。

原则:LSP 里氏替换原则

场景:创建不同的产品对象,客户端应使用不同的具体工厂。

优点:

     a)   改变具体工厂即可使用不同的产品配置,使改变一个应用的具体工厂变得很容易。

     b)   让具体的创建实例过程与客户端分离,客户端通过抽象接口操作实例,产品的具体类名也被具体工厂的实现分离。

缺点:如果要新增方法,改动极大。

应用:

      a)  jdk中连接数据库的代码是典型的抽象工厂模式,每一种数据库只需提供一个统一的接口:Driver(工厂类),并实现其中的方法即可。不管是jdbc还是odbc都能够通过扩展产品线来达到连接自身数据库的方法。

      b)  java.util.Collection 接口中定义了一个抽象的 iterator() 方法,该方法就是一个工厂方法。对于 iterator() 方法来说 Collection 就是一个抽象工厂。

 

建造者模式(Builder) 【又名,生成器模式】:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

原则:依赖倒转原则

场景:如果需要将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。建造者模式是当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时适用的模式。

优点:使得建造代码与表示代码分离。

缺点:1、增加代码量;2、Builder只是一个替代构造器的选择,不能直接用于降低非构造函数方法的参数数量。

应用:StringBuilder和StringBuffer的append()方法

工厂方法模式(Factory Method) 定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。

原则:开放封闭原则

场景:不改变工厂和产品体系,只是要扩展产品(变化)。

优点:是简单工厂模式的进一步抽象和推广,既保持了简单工厂模式的优点(工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类。对于客户端来说,去除了与具体产品的依赖),而且克服了简单工厂的缺点(违背了开放封闭原则)。

缺点:每增加一个产品,就需要增加一个产品工厂的类,增加了额外的开发。(用反射可以解决)。

应用:

1.       Collection中的iterator方法;

2.       java.lang.Proxy#newProxyInstance()

3.       java.lang.Object#toString()

4.       java.lang.Class#newInstance()

5.       java.lang.reflect.Array#newInstance()

6.       java.lang.reflect.Constructor#newInstance()

7.       java.lang.Boolean#valueOf(String)

8.       java.lang.Class#forName()

 

原型模式(Prototype) 【又名,生成器模式】:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

原则:

场景:在初始化信息不发生变化的情况,用克隆进行拷贝。

优点:隐藏了对象创建的细节,大大提升了性能。不用重新初始化对象,而是动态的获得对象运行时的状态。

缺点:深复制 or 浅复制 。

应用:JDK中的Date类。

  

单例模式(Singleton) 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

原则:封装

场景:通常,我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象,一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,而且它可以提供一个访问该实例的方法。

优点:对唯一实例的受控访问。

缺点:饿汉式/懒汉式  多线程同时访问时可能造成多个实例。

应用:java.lang.Runtime; GUI中也有一些(java.awt.Toolkit#getDefaultToolkit() java.awt.Desktop#getDesktop())

 

适配器模式(Adapter) 将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

在GoF的设计模式中,适配器有两种类型,类适配器模式和对象适配器模式。

    a)   类适配器模式:通过多重继承对一个接口与另一个接口进行匹配,而C#,Java等语言都不支持多重继承,也就是一个类只有一个父类。

    b)   Java一般都指的是 对象适配器模式

场景:适配器是为了复用一些现有的类。系统的数据和行为都正确,但是接口不符,这时采用适配器模式,使原有对象和新接口匹配。

优点:能够复用现存的类,客户端统一调用同一接口,更简单、直接、紧凑。

缺点:适配器模式有点儿“亡羊补牢”的感觉,设计阶段要避免使用。

应用:在Java jdk中,适配器模式使用场景很多,如集合包中Java.util.Arrays#asList()、IO包中java.io.InputStreamReader(InputStream)、java.io.OutputStreamWriter(OutputStream) 等

 

桥接模式(Bridge) 将抽象部分与它的实现部分分离,使它们都可以独立的变化。

原则:合成/聚合复用原则

场景:实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。

优点:减少各部分的耦合。 分离抽象和实现部分,更好的扩展性,可动态地切换实现、可减少子类的个数。

缺点:1、桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。 2、桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性

应用:Collections类中的sort()方法;AWT;JDBC数据库访问接口API;

  

组合模式(Composite) 将对象组合成树形结构以表示“部分-整体”的层次结构。

场景:需求中体现部分与整体层次结构时,以及希望用户可以忽略组合对象与单个对象的不同,统一使用组合结构中的所有对象时,就应该考虑使用组合模式了。

优点:组合模式让客户可以一致的使用组合结构和单个对象。

缺点:使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联。

应用:JDK中AWT包和Swing包的设计是基于组合模式,在这些界面包中为用户提供了大量的容器构件(如Container)和成员构件(如Checkbox、Button和TextComponent等),他们都是继承、关联自抽象组件类Component。

 

装饰模式(Decorator) 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。

场景:装饰模式是为了已有功能动态地添加更多功能的一种方式,当系统需要新功能的时候,是向旧类中添加新的代码,这些新的代码通常装饰了原有类的核心职责或主要行为。装饰着模式把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择的、按顺序地使用装饰功能包装对象。

优点:把类中的装饰功能从类中搬移出去,简化原有的类。有效的把类的核心职责和装饰功能区分开,去除相关类中重复的装饰逻辑。

缺点:利用装饰器模式,常常造成设计中有大量的小类,数量实在太多,可能会造成使用此API程序员的困扰。

应用:Java I/O使用装饰模式设计,JDK中还有很多类是使用装饰模式设计的,如:Reader类、Writer类、OutputStream类等。

 

外观模式(Facade) 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

原则:完美的体现了依赖倒转原则和迪米特法则。

场景:

    a)   设计阶段:需有意识的将不同的两个层分离。

    b)   开发阶段:增加外观façade提供一个简单的接口,应对子类的重演和演化。

    c)   维护期间:使用façade类,为遗留代码提供清晰简单的接口,让新系统与façade交互,façade与遗留代码交互所有复杂的工作。

优点:1、客户对子系统的使用变得简单了,减少了与子系统的关联对象,实现了子系统与客户之间的松耦合关系。 2、只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类 3、降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程。

缺点:1、不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性   2、在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

 

享元模式(Flyweight) 运用共享技术有效的支持大量细粒度的对象。

场景:如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大存储开销时就应该考虑使用享元模式;还有就是对象大多数状态都可为外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。

优点:享元模式可以避免大量非常相似类的开销。程序中,大量细粒度的类实例来表示数据,如果它们除了几个参数外基本相同,那么把它们转移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度减少单个实例的数目。

缺点:1、由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。2、为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

应用:String 类。

 

代理模式(Proxy) 为其他对象提供一种代理以控制对这个对象的访问。

原则:代理模式就是在访问对象时引入一定程度的间接性。(迪米特法则?)

场景:

          a)   远程代理:为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。【WebService,客户端可以调用代理解决远程访问问题】

          b)   虚拟代理:根据需要创建开销很大的对象,通过它来存放实例化需要很长时间地真实对象。【比如Html网页的图片,代理存储的是真实图片的路径和尺寸】

          c)   安全代理:用来控制真实对象的访问权限。

          d)   智能指引:当调用真实的对象时,代理处理另一些事。【如计算机真实对象的引用次数,代理在访问一个对象的时候回附加一些内务处理,检查对象是否被锁定、是否该释放、是否该装入内存等等】

优点:1)代理模式能将代理对象与真正被调用的对象分离,在一定程度上降低了系统的耦合度。

           2)代理模式在客户端和目标对象之间起到一个中介作用,这样可以起到保护目标对象的作用。代理对象也可以对目标对象调用之前进行其他操作。

缺点:1)在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。

           2)增加了系统的复杂度。

应用:java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。

  

观察者模式(Publish/Subscribe) 【又名 发布-订阅模式】:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,让它们能够自动更新自己。

场景:将一个系统分割成一系列互相协作的类,有一个缺点:需要维护相关对象间的一致性。紧密的耦合会给维护和扩展带来不便。观察者模式就是为了解耦而诞生的,让原本一个对象依赖另一个对象的关系,变成了两方都依赖于抽象,而不再依赖于具体,从而使得各自的变化都不会影响另一边的变化。

优点:解耦。

缺点:如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。

应用:java.util.Observer ,  java类库实现观察着(Observer)模式的类和接口。

  

模板方法模式(Template Method) 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。

原则:代码复用平台。

场景:遇到由一系列步骤构成的过程需要执行,这个过程从高层次上看是相同的,但是有些步骤的实现可能不同,这个时候就需要考虑用模板方法模式了。

优点:模板方法模式是通过把不变行为搬移到超类,去除子类中重复代码来实现它的优势,提供了一个代码复用平台,帮助子类摆脱重复的不变行为的纠缠。

缺点:如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大。

应用:AbstractClass抽象类里面的TemplateMethod()就是模板方法。

 

命令模式(Command) 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

原则:敏捷开发原则

场景:对请求排队或记录请求日志,以及支持可撤销的操作等行为。

优点:

          a)      命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。

          b)      它能较容易的设计一个命令队列。

          c)       在需要的情况下,可以较容易的将命令记入日志。

          d)      允许接收请求的一方决定是否要否决请求。

          e)      可以容易的实现对请求的撤销和重做。

          f)        由于加进新的具体命令类不影响其他类,因此增加新的具体命令类很容易。

缺点:会增加系统的复杂性,这里的复杂性应该主要指的是类的数量。

应用:

          1.  java.util.Timer类中scheduleXXX()方法

          2.  java Concurrency Executor execute()方法

          3.  java.lang.reflect.Methodinvoke()方法

 

状态模式(State) 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。

原则:单一职责原则

场景:当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,可以考虑使用状态模式了。

优点:状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。【消除庞大的条件分支语句】。

缺点:违背开放-封闭原则

应用:

          1.  java.util.Iterator

          2. javax.faces.lifecycle.LifeCycle#execute()

 

职责链模式(Chain of responsibility) 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

场景:当客户提交一个请求时,请求是沿链传递直至有一个对象负责处理它。

优点:使得接收者和发送者都没有对方的明确信息,且链中对象自己也不知道链结构,结果是职责链可以简化对象的相互连接,它们只需要保持一个指向其后继者的引用,而不需  要保持它所有的候选接收者的引用。开发者可以随时的增加或者修改处理一个请求的结构,增强了给对象指派职责的灵活性。

缺点:一个请求极有可能到了链的末端都得不到处理,或者因为没有正确配置而得不到处理。

  

解释器模式(Interpreter) 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

原则:依赖倒转原则

场景:如果一种特定类型问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语句中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。当一个语言需要执行,并且你可将该语言中的句子表示为一个抽象语法树时,可以用解释器模式。

优点:解释器很容易改变和扩展文法,因为该模式使用类来表示文法规则,可以使用继承来改变或扩展文法,也比较容易实现文法。因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写。

缺点:解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护,建议当文法非常复杂时,使用其他技术(语法分析程序、编译器生成器)。

应用:

          1. java.util.Pattern

          2.  java.text.Normalizer

          3.  java.text.Format

          4.  javax.el.ELResolver

 

中介者模式(Mediator) 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变它们之间的交互。

场景:一般应用于一组对象以定义良好但是复杂的方式进行通信的场合,以及想定制一个分布在多个类的行为,而又不想生成太多子类的场合。【例如,Form窗体,或者aspx页面】。

优点:

          a)   抽象中介者类(Mediator)减少了抽象同事类(colleague)之间的耦合,是的可以独立的改变和复用各个类。

          b)   由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到它们之间的交互上来,也就是站在一个更宏观的角度去看待系统。

缺点:控制集中化导致了中介者的复杂化。

应用:

          1.   java.util.Timer

          2.   java.util.concurrent.Executor#execute()

          3.   java.util.concurrent.ExecutorService#submit()

          4.   java.lang.reflect.Method#invoke()

  

访问者模式 (Vistor) 生成器模式】:(GoF中最复杂的一个模式)表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

场景:访问者模式适合有稳定的数据结构、又有易于变化的算法】访问者模式适用于数据结构相对稳定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,是的操作集合可以相对自由的演化。访问者模式的目的是要把处理从数据结构中分离出来。

优点:增加新的操作很容易。新的操作就是新的访问者。

缺点:很难增加新的数据结构。

应用:

          1.   javax.lang.model.element.AnnotationValue和AnnotationValueVisitor

          2.   javax.lang.model.element.Element和ElementVisitor

          3.   javax.lang.model.type.TypeMirror和TypeVisitor

  

策略模式(Strategy) 它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的用户。

场景:策略模式不仅可以用来封装算法,几乎可以封装缝合类型的规则,不同的业务逻辑都可以考虑用策略模式处理变化。

优点:策略模式的策略类为上下文定义了一系列可供重用的算法或行为,继承有助于析取出这些算法中的公共功能。另外,策略模式简化了单元测试,因为每一个算法都有自己的类,可以通过自己的接口单独测试。当不同的行为堆砌在一个类中,很难避免使用switch语句。但是将这些行为封装在一个一个独立的策略类中,可以在使用这些行为的类中消除条件语句

缺点:基本的策略模式,选择权在客户端,具体实现转给策略模式的上下文对象。这并不好。使用策略模式和工厂类结合,可以减轻客户端的职责。但是还是不够完美,使用反射才能真正快乐。

应用:

          1.   java.util.Comparator#compare()

          2.    javax.servlet.http.HttpServlet

          3.    javax.servlet.Filter#doFilter()

 

备忘录模式(Memento) 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。

场景:Memento封装要保存的细节,适合功能负责但需要维护或记录属性历史的类,或者是需要保存的属性只是众多属性中的一个小部分。

优点:使用备忘录模式可以把复杂的发起人内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界。

缺点:如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。

应用:

          1.   java.util.Date

          2.   java.io.Serializable

  

迭代器模式(Iterator) 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

场景:当需要对聚集有多种方式遍历时,可以考虑使用迭代器。

优点:迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器来负责,这样既可以做到不暴露集合的内部结构,又可以让外部代码透明的访问集合内部的数据。

缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

应用:collection容器使用了迭代器模式

转载于:https://www.cnblogs.com/DreamRecorder/p/10641052.html

分布式 

soa   分布式锁  分布式事物  一致性 高可用  分区容错性  C一致性 A可用性 P分区容错性  

 

mysql 

索引  MVCC  事物的隔离级别

索引

innoDb 引擎

B树 B+树

叶子节点 B+树是有指针(链表)相连的   非叶子节点冗余数据  节点中可以存多个数据

页  操作系统也有这个概念   --逻辑单位  从磁盘读取数据时最小基本单位 一页=4kb 数据库innodb默认16k

 

四个关键隐藏字段

当前事物id  隐藏主键rownum  上一条记录指针  是否删除标记   

undo日志  快照读实现的mvcc

总之在RC(读已提交)隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR(可重复读)隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。

 

网络协议

TCP协议 UDP协议

 

算法

kmp  大数据  ip访问量处理  topk问题

 

高可用处理方式    

高并发原则

无状态   拆分  服务化   队列  数据异构  缓存   并发化

高可用原则

降级  限流  切流量  可回滚

业务设计原则

防重  幂等性  状态  流程可定义  备份等

 

缓存穿透 

 缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决

接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
 

缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力

  1. 设置热点数据永远不过期。
  2. 加互斥锁

缓存雪崩

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,        缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

     解决方案:

缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
设置热点数据永远不过期。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值