
目录
第一部分、JAVA容器
- Iterable 接口
- Collection 接口
- List 接口
- Set接口
- Queue 接口
- Map 接口
第二部分、JAVA基础知识
- 封装、继承、多态
- 对象与类
- 反射:反射机制+反射API+优缺点+JVM
- JAVA异常
- Java注解
- JAVA自动拆装箱
- JAVA泛型
- JDK、JRE
第三部分、JAVA并发编程
- voliate关键字
- 内存模型(JMM)
- 线程池
- 线程
第四部分、JVM虚拟机
第五部分、Spring
第六部分、设计模式
第一部分 Java容器

注:map是独立出来的接口,没有继承collection
一、Iterable 接口
-
只有一个方法: Iterator
iterator() //即返回一个迭代器 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象
(1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
(2) 使用next()获得序列中的下一个元素。
(3) 使用hasNext()检查序列中是否还有元素。
(4) 使用remove()将迭代器新返回的元素删除。
二、Collection 接口
- 表示一组对象(元素),有些允许重复,有些不允许重复
- JDk不提供该接口的实现,提供更具体的子接口的 实现
- 不同的Collection子类对于有序性、重复性、null、线程同步都有不同的策略
三、List 接口
- 有序、不允许重复
- 用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引访问元素,并搜索列表中的元素。
- 提供了特殊的迭代器,称为 ListIterator,允许元素插入和替换,以及双向访问,还提供了一个方法来获取从列表中指定位置开始的列表迭代器ListIterator
listIterator(int index)。 - 子类:
(1)ArrayList:
- 基于数组实现的List类,封装了一个动态的、增长的、允许再分配的Object[ ]数组。
- 当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
- 默认扩容50%,线程不安全
(2)Vector :
- 通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢
- Stack是Vector提供的一个子类,用于模拟"栈"这种数据结构
- 默认扩容一倍,线程安全
(3)LinkedList
- LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。
- 还实现了Deque接口,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
四、Set接口
- 满足集合的无序性,确定性,单一性,如果有多个null,则不满足单一性了,所以Set只能有一个null。元素没有先后的差别。
- Set判断两个对象相同不是使用"=="运算符,而是根据equals方法。
- 子类
(1)HashSet :
- 使用HASH算法来存储集合中的元素,因此具有良好的存取和查找性能。
- HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法的返回值相等
- LinkedHashSet:同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时(遍历)将有很好的性能(链表很适合进行遍历)
(2) SortedSet (接口):主要用于排序操作,实现了此接口的子类都属于排序的子类
- TreeSet:可以确保集合元素处于排序状态
(3)EnumSet
- 专门为枚举类设计的集合类,所有元素都必须是指定枚举类型的枚举值
五、Queue 接口
- 用于模拟“队列”数据结构(FIFO)
- 子类:
(1)riorityQueue—— 优先队列:按照队列中某个属性的大小来排列的。故而叫优先队列
(2)Deque——双端队列(接口):
- ArrayDeque:基于数组的双端队列,类似于ArrayList有一个Object[] 数组
- LinkedList
六、Map 接口
- 不是collection的子接口或者实现类,本身是一个接口
- 用于保存具有“映射关系”的数据。每个Entry都持有键-值两个对象。其中,Value可能重复,但是Key不允许重复,可以有多个Value为null,但是只能有一个Key为null。
- 子类:
(1)HashMap:
- 不能保证元素的顺序一样,也不能保证key-value对的顺序,通过equals()方法比较
- LinkedHashMap:使用双向链表来维护key-value对的次序,与插入顺序一致
(2) HashTable:
- 子类:Properties---可以把Map对象和属性文件关联,从而把Map对象的key - value对写入到属性文件中,也可把属性文件中的“属性名-属性值”加载进Map对象中
(3)SortedMap(接口)
- TreeMap:红黑树结构,每个键值对都作为红黑树的一个节点,需要根据key对节点进行排序,可以保证所有的key-value对处于有序状态。两种排序方式:自然排序、定制排序
(4)WeakHashMap:HashMap的key保留了对实际对象的强引用,这意味着只要该HashMap对象不被销毁,该HashMap所引用的对象就不会被垃圾回收。但WeakHashMap的key只保留了对实际对象的弱引用,这意味着如果WeakHashMap对象的key所引用的对象没有被其他强引用变量所引用,则这些key所引用的对象可能被垃圾回收,当垃圾回收了该key所对应的实际对象之后,WeakHashMap也可能自动删除这些key所对应的key-value对。
(5)IdentityHashMap:当且仅当两个key严格相等(key1==key2)时,IdentityHashMap才认为两个key相等
(6)EnumMap:与枚举类一起使用的Map实现,EnumMap中的所有key都必须是单个枚举类的枚举值。根据key的自然顺序存储。
hashmap底层实现
一、存储结构-字段
-
数组+链表+红黑树(JDK1.8增加了红黑树部分)
-
控制map使得Hash碰撞的概率又小,哈希桶数组(Node[] table)占用空间又少:好的Hash算法和扩容机制
-
构造函数
int threshold; // 所能容纳的key-value对极限;threshold = length * Load final float loadFactor; // 负载因子 int modCount; int size; -
当链表长度太长(默认超过8)时,链表就转换为红黑树
二、功能实现-方法
-
确定哈希桶数组索引位置:取key的hashCode值、高位运算、取模运算。
-
put方法:
①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
-
扩容机制
- 使用一个容量更大的数组来代替已有的容量小的数组,transfer()方法将原有Entry数组的元素拷贝到新的Entry数组里。
- 扩容到下一个2的指数幂
- 线程不安全,会出现环,造成死循环
- 为什么建立hashmap时需要初始化大小:如果我们没有设置初始容量大小,随着元素的不断增加,HashMap会发生多次扩容,而HashMap中的扩容机制决定了每次扩容都需要重建hash表,是非常影响性能的。
- 没有设置初始化容量,系统会默认创建一个容量为16的大小的集合。
-
线程不安全
- 数据丢失:如果有两条线程同时执行到这条语句table[i]=null,时两个线程都会区创建Entry,这样存入会出现数据丢失。
- 数据重复:如果有两个线程同时发现自己都key不存在,而这两个线程的key实际是相同的,在向链表中写入的时候第一线程将e设置为了自己的Entry,而第二个线程执行到了e.next,此时拿到的是最后一个节点,依然会将自己持有是数据插入到链表中,这样就出现了数据重复。
- 死循环
CurrentHashMap底层原理
一、实现原理
-
解决hashmap在并发环境下不安全
-
JDK1.8:synchronized+CAS+HashEntry+红黑树;
-
JDK1.7:ReentrantLock+Segment+HashEntry。
-
JDK7 ConcurrentHashMap
- 由Segment(分段锁)数组结构和HashEntry数组组成
- 一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构,因此在ConcurrentHashMap查询一个元素的过程需要进行两次Hash操作
- 第一次Hash定位到Segment,
- 第二次Hash定位到元素所在的链表的头部
- 将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问
-
JDK8 ConcurrentHashMap
- 数组+链表+红黑树
- 基于CAS操作保证数据的获取以及使用synchronized关键字对相应数据段加锁来实现线程安全
LinkedHashMap原理
- 用处:在使用HashMap的时候,可能会遇到需要按照当时put的顺序来进行哈希表的遍历,HashMap中不存在保存顺序的机制。于是使用LinkedHashMap
- 可以保持两种顺序,分别是插入顺序和访问顺序
- 原理:通过哈希表和链表实现的,它通过维护一个链表来保证对哈希表迭代时的有序性
第二部分 JAVA基础
JAVA IO/NIO
一、I/O
- 流的概念(Stream):指一连串的数据(字符或字节),是以先进先出的方式发送信息的通道。
- 特性:先进先出、顺序存取、只读或只写
- 分类:
- 按数据流的方向:输入流、输出流
- 按处理数据单位:字节流、字符流
- 字节流和字符流所操作的数据单元不同
- 字节流可以处理一切文件,而字符流只能处理纯文本文件。
- 字节流本身没有缓冲区,缓冲字节流相对于字节流,效率提升非常高。而字符流本身就带有缓冲区,缓冲字符流相对于字符流效率提升不大
- 按功能:
- 节点流:直接操作数据读写的流类
- 处理流:对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能
- 都应用了Java的装饰者设计模式
- 处理流中的缓冲流:较为重要
- 使用I/O
- FileInputStream、FileOutputStream(字节流)
- BufferedInputStream、BufferedOutputStream(缓冲字节流)
- InputStreamReader、OutputStreamWriter(字符流)
- 字符流便捷类:FileWriter和FileReader简化字符流的读写
- BufferedReader、BufferedWriter(字符缓冲流)
- IO流对象:
- File类
- 字节流(InputStream
与OutputStream是两个抽象类,是字节流的基类): - 字符流(Reader
和Writer抽象基类) - 序列化:详见序列化章节
二、NIO
- 概念:NIO 与原来的 IO 有同样的作用和目的,但是使用方式完全不同,NIO 支持面向缓冲区的、基于通道的 IO 操作。NIO 将以更加高效的方式进行文件的读写操作。
- 通道和缓冲区和Selector:Channel 负责传输,Buffer 负责存储,Selector(选择区)用于监听多个通道的事件
- 通道本身不能传输数据,要想传输数据必须要有缓冲区;把数据都写到缓冲区,然后缓冲区通过通道进行传输,最后再把数据从缓冲区拿出来写到文件中
- 双向,传统的IO是单向的
- 分散读取与聚集写入:
- 分散读取(Scattering Reads)是指从 Channel 中读取的数据 "分散" 到多个 Buffer 中
- 聚集写入(Gathering Writes)是指将多个 Buffer 中的数据 "聚集" 到 Channel
String类
-
源码:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count; /** Cache the hash code for the string */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; ........ }-
定义为final类
-
String是引用类型。所以 String 对象存储在堆空间,它的引用在栈空间
-
不可变:String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象
好处:
- 状态不可变的对象可以保证线程安全,不需要任何加锁操作保证线程安全,提高系统性能。
- 不可变对象才能实现字符串常量池,将相同字面量放入常量池 同一块内存地址。
-
-
String的比较
-
对于引用类型来说,“==” 比较地址是否相同,当 String 调用 equals 比较时,会比较字符串字面量是否相同
-
源码中 String 类将 equals 方法重写,判断两个字符串的每一个字符都相同则返回 true。
-
根类 Object 中,equals 和 “” 是等价的,所以 equals 会对比出什么结果,取决于子类如何重写,如果不重写,默认和 “”是等价的
public boolean equals(Object obj) { return (this == obj); }
-
-
String 常量池
-
当使用字面量形式声明对象时,会先检查字符串常量池中是否已经存在该对象,如果已存在直接将引用指向已存在的对象,如果不存在,将该对象放入常量池。
-
当使用 new 关键字声明对象时,会先检查字符串常量池中是否已经存在该对象,如果已存在直接将引用指向已存在的对象,如果不存在,将该对象放入常量池。此外还会在堆空间开辟一块内存地址。并且将该引用指向堆中的地址
-
由于 new 关键字会在堆中开辟空间,所以开发中一般情况不建议使用,直接用字面量形式声明即可
String str1 = "Hello";//产生1个对象放入常量池 String str2 = new String("World");//创建两个对象,一个在堆,一个在常量池 String str3 = new String("Hello");//创建一个对象在堆,由于常量池已经有 Hello 不会再创建
-
-
String 类的“加法”:对于 String 类的 “+” 和 "+=" 两个特殊重载
-
“+” 将两个字符串连接生成一个新的对象,也就是内存中新开辟了一块空间
-
当使用 "+" 对两个字符串常量连接时,由于 "+" 号两边都是常量,因此会直接连接放入常量池
“+” 两边并不全是常量,有一个是变量,这样它的结果是不能在编译期间确定的,只有在运行时才知道
intern():这个方法是个 native 方法,将调用它的对象尝试放入常量池,如果常量池已经有了,就返回指向常量池中的引用,如果没有就放入常量池,并且返回指向常量池中的引用。
String str1 = "Hello"; String str2 = "Hello" + "World"; String str3 = str1 + "World"; str3 = str3.intern();
-
-
String 、StringBuilder、StringBuffer
- StringBuilder:可以在原字符串的基础上进行增删改,并且不会新开辟内存空间。
- 由于 String 对象不可变,每一个操作都会产生新的对象,这样似乎不太友好,可能会造成内存占用过大,而且会频繁创建对象。
- String 内部维护了一个 final char[] value,不可变
- StringBuilder 内部维护了一个 char[] value 没有用 final 修饰
- String 的 “+”:底层会 new 一个 StringBuilder 对象调用 append 来连接字符串,生成一个新的 String 对象
- StringBuffer:对于字符串的增删改方法都加上了 synchronized 关键字,对于字符串的操作是线程安全的,由于线程安全所以其性能也次于 StringBuilder。
- StringBuilder:可以在原字符串的基础上进行增删改,并且不会新开辟内存空间。
封装、继承、多态
一、封装
- 把对象的属性和方法结合成一个独立的整体,隐藏实现细节,并提供对外访问的接口
- 隐藏实现细节、安全性、增加代码复用性、模块化
二、继承
-
从已知的一个类中派生出一个新的类,叫子类。子类实现了父类所有非私有化的属性和方法,并根据实际需求扩展出新的行为。
-
优点
1.继承是传递的,易于在其基础上构造和扩充。
2.简化对事物的描绘,使得层次更加清晰。
3.减少代码冗余。
4.提高可维护性。
-
覆盖方法:关键字 super--调用父类方法
-
多态
- 是否应该设计为继承关系: “ is - a ” 规则 , 它表明子类的每个对象也是超类的对象;程序中出现超类对象的任何地方都可以用子类对象置换
- 一个Employee 变量既可以引用一个Employee 类对象 , 也可以引用一个 Employee 类的任何一个子类的对象
- 不能将一个超类的引用赋给子类变量
-
阻止继承 : final 类和方法
-
强制类型转换:只能在继承层次内进行类型转换 。在将超类转换成子类之前 , 应该使用instanceof 进行检查。
-
抽象类:abstract;抽象类还可以包含具体数据和具体方法 。
- 扩展抽象类可以有两种选择 。一种是在抽象类中定义部分抽象类方法或不定义抽象类方法 , 这样就必须将子类也标记为抽象类 ; 另一种是定义全部的抽象方法
, 这样一来 , 子类就不是抽象的了 - 抽象类不能被实例化
- 扩展抽象类可以有两种选择 。一种是在抽象类中定义部分抽象类方法或不定义抽象类方法 , 这样就必须将子类也标记为抽象类 ; 另一种是定义全部的抽象方法
-
受保护访问:project;超类中的某些方法允许被子类访问 ,或允许子类的方法访问超类的某个域。
-
Object : 所有类的超类
- equals 方法;判断两个对象是否具有相同的引用
- hashCode 方法:散列码 ( hash code ) 是由对象导出的一个整型值
- 如果重新定义 equals 方法, 就必须重新定义hashCode 方法
- toString 方法:返回表示对象值的字符串
三、多态
-
多个不同的对象对同一消息作出响应。同一消息根据不同的对象而采用各种不同的方法。
(程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定)
-
优点:主要是利于扩展
-
实现条件:
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
-
实现形式
- 基于继承实现的多态:对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的所有子类,子类对象的不同,对方法的实现也就不同,执行相同动作产生的行为也就不同。
- 基于接口实现的多态:指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。
对象与类
-
静态域和静态方法
- 静态域:static,每个类中只有一个这样的域,而对象对于实例域都有自己的一份拷贝
- 静态方法:不能用对象操作,直接用类使用方法
-
对象构造
- 重载:多个方法有相同的名字,不同的参数
- 默认域初始化:如果在构造器中没有显式地给域赋予初值 , 那么就会被自动地赋为默认值 : 数值为 0 、布尔值为 false、对象引用为 null 。
- 无参数的构造器:如果没有编写构造器 , 那么系统就会提供一个无参数构造器;如果提供了至少一个构造器 , 但是没有提供无参数的构造器 , 则在构造对象时如果没有提供参数就会被视为不合法
- 显式域初始化
- 初始化块:在一个类的声明中 ,可以包含多个代码块。 只要构造类的对象 , 这些块就会被执行;对类的静态域进行初始化可以使用静态的初始化块
- 对象析构与 finalize 方法:Java 有自动的垃圾回收器, 所以Java 不支持析构器; 某些对象使用了内存之外的其他资源:加 finalize 方法。 finalize 方法将在垃圾回收器清除对象之前调用
反射:反射机制+反射API+优缺点+JVM
-
一种能够在程序运行时动态访问、修改某个类中任意属性和方法的机制
-
用处:根据类名创建实例;用Method.invoke执行方法
-
原理:当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已;反射的工作原理就是借助Class.java、Constructor.java、Method.java、Field.java这四个类在程序运行时动态访问和修改任何类的行为和状态。
-
四个核心类
java.lang.Class.java:类对象;
java.lang.reflect.Constructor.java:类的构造器对象;
java.lang.reflect.Method.java:类的方法对象;
java.lang.reflect.Field.java:类的属性对象;
-
JVM是如何构建一个实例的

-
类加载器:loadClass(),告诉它需要加载的类名,它会帮你加载
检查是否已经加载,有就直接返回,避免重复加载
当前缓存中确实没有该类,那么遵循父优先加载机制,加载.class文件
上面两步都失败了,调用findClass()方法加载
-
反射API
(1)获取反射中的Class对象
1.Class clz = Class.forName("java.lang.String");//知道该类的全路径名 2.Class clz = String.class;//适合在编译前就知道操作的 Class 3.String str = new String("Hello"); Class clz = str.getClass();//getClass方法(2)通过反射创建类对象
//1.Class 对象的 newInstance() 方法 Class clz = Apple.class; Apple apple = (Apple)clz.newInstance(); //2.Constructor 对象的 newInstance() 方法 Class clz = Apple.class; Constructor constructor = clz.getConstructor(); Apple apple = (Apple)constructor.newInstance(); //注:通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法(3)通过反射获取类属性、方法、构造器
//通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性 Class clz = Apple.class; Field[] fields = clz.getFields(); for (Field field : fields) { System.out.println(field.getName()); } //使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性 Class clz = Apple.class; Field[] fields = clz.getDeclaredFields(); for (Field field : fields) { System.out.println(field.getName()); } -
优点:灵活、自由度高:不受类的访问权限限制,想对类做啥就做啥;
-
缺点:
性能问题:通过反射访问、修改类的属性和方法时会远慢于直接操作,但性能问题的严重程度取决于在程序中是如何使用反射的。如果使用得很少,不是很频繁,性能将不会是什么问题;
安全性问题:反射可以随意访问和修改类的所有状态和行为,破坏了类的封装性,如果不熟悉被反射类的实现原理,随意修改可能导致潜在的逻辑问题;
兼容性问题:因为反射会涉及到直接访问类的方法名和实例名,不同版本的API如果有变动,反射时找不到对应的属性和方法时会报异常;
-
实例
Class clz = Class.forName("com.chenshuyi.reflect.Apple"); Method method = clz.getMethod("setPrice", int.class); Constructor constructor = clz.getConstructor(); Object object = constructor.newInstance(); method.invoke(object, 4);Apple apple = new Apple(); //直接初始化,「正射」 apple.setPrice(4);
JAVA异常

一、分类
- Error:运行时系统的内部错误和资源耗尽错误
- Exception:
- RuntimeException:可能在 Java 虚拟机正常运行期间抛出的异常的超类
- CheckedException:一般是外部错误,这种异常都发生在编译阶段
二、处理
- Throw 和 throws 的区别:
- 位置:throws 用在函数上,后面跟的是异常类,可以跟多个;而 throw 用在函数内,后面跟的是异常对象。
- 功能:throws 用来声明异常,让调用者只知道该功能可能出现的问题,可以给出预先的处理方式;throw抛出具体的问题对象,执行到throw,功能就已经结束了
- 发生:throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw 则是抛出了异常,执行 throw 则一定抛出了某种异常对象
- 同:消极处理异常
Java注解
- 概念:Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。Annatation(注解)是一个接口,程序可以通过反射来获取指定程序中元素的 Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息
- 对于注解,是有专门的程序去读取它,解析它,然后根据得到的消息去执行相应的操作。
- 注释是给人看到的,注解是给程序看的
- 注解的本质是个接口
- 注解类型:
- 自定义注解(实际很少)
- JDK内置注解(@Override检验方法重写)
- 框架中的注解
- 注解流程:定义注解、使用注解、读取并执行相应流程(通过反射机制)
- 4 种标准元注解
- @Target:说明了Annotation所修饰的对象范围
- @Retention:定义了该 Annotation 被保留的时间长短
- @Documented:描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API(用于制作文档)
- @Inherited: 阐述了某个被标注的类型是被继承的
- @Repeatable:JDK 8中新加入,该注解可以在同一个地方被重复使用多次,用
@Repeatable来修饰注解时需要指明一个接受重复注解的容器
- 注解处理器
JAVA自动拆装箱
- 目的:原始类型值转自动地转换成对应的对象
- 概念:Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。原始类型byte,short,char,int,long,float,double和boolean对应的封装类为Byte,Short,Character,Integer,Long,Float,Double,Boolean。
- 要点:自动装箱时编译器调用valueOf将原始类型值转换成对象,同时自动拆箱时,编译器通过调用类似intValue(),doubleValue()这类的方法将对象转换成原始类型值。
- 发生时机:赋值时、方法调用时
- 缺点:一个循环中进行自动装箱操作的情况,就会创建多余的对象,影响程序的性能。
- 重载与自动装箱
- 注意事项:对象相等比较、容易混乱的对象和原始数据值、缓存的对象、生成无用对象增加GC压力
JAVA泛型
- 概念::泛型就是把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型。
- 泛型里面数据类型不能是基本类型:虚拟机在编译时会把带泛型的转换成Object类型,而基本类型不属于Object类型,所以泛型里面数据类型不能是基本类型。
- 使用泛型的原因:安全简单、在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
- 泛型只存在于编译期:泛型的信息不会进行运行阶段
- 使用方式:
- 泛型类:
public class Test<T>}{}T表示未知类型 - 泛型接口:
public interface Test<T>{}和定义类一样 - 泛型方法:
public <T> void Test(T name){}
- 泛型类:
- 泛型通配符(?)
- 类型擦除:Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java 字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除
JDK、JRE
一、JAVA8新特性
- Lambda 表达式:Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)
- 函数式接口:指的是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,这样的接口可以隐式转换为 Lambda 表达式
- 方法引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码
- 默认方法:默认方法就是一个在接口里面有了一个实现的方法
- Stream API:新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Optional 类:Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Date Time API:加强对日期与时间的处理。
- Nashorn, JavaScript 引擎:Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用
第三部分 JAVA并发编程
voliate关键字
-
voliate关键字的作用:
-
内存可见性:保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
-
基于内存屏障的防止指令重排:底层的实现方式是基于4种内存屏障:读读、读写、写读、读读屏障。
指令重排序:一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
-
-
对性质的保障
- 无法保证原子性,虽然可以使得变量立即可见,但是无法保证操作具有原子性
- volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性
-
原理和实现机制:(如何保证可见性和禁止指令重排序的)
内存屏障:
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
-
使用volatile关键字的场景(需要保证操作是原子性操作,才能保证并发执行正确)
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
线程安全
- 概念:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
- 操作共享数据:不可变、绝对线程安全、相对线程安全、线程兼容、线程对立
- 线程安全实现方法:
- 互斥同步:synchronized关键字
- 非阻塞同步:基于冲突检测的乐观并发策略
- 无同步方案
- 可重入代码
- 线程本地存储
内存模型(JMM)
- 概念:是一种虚拟机规范(标准之类的),在并发过程中如何处理原子性、可见性和有序性这 3 个特征来建立
- 目的:屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。
- 主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作:
- lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
- unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
- write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
且要满足以下规则:
- 如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
- 不允许read和load、store和write操作之一单独出现
- 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
- 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
- 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
- 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。
-
Happens-Before:
- 程序顺序规则:如果程序中操作 A 在操作 B 之前,那么在线程中操作 A 将在操作 B 之前执行。
- 监视器锁规则:在监视器锁上的解锁操作必须在同一个监视器锁上的加锁操作之前执行。
- volatile 变量规则:对 volatile 变量的写入操作必须在对该变量的读操作之前执行。
- 线程启动规则:在线程上对 Thread.start 的调用必须在该线程中执行任何操作之前执行。
- 线程结束规则:线程中的任何操作都必须在其他线程检测到该线程已经结束之前执行,或者从 Thread.join 中成功返回,或者在调用 Thread.isAlive 时返回 false。
- 中断规则:当一个线程在另一个线程上调用 interrupt 时,必须在被中断线程检测到 interrupt 调用之前执行(通过抛出 InterruptException,或者调用 isInterrupted 和 interrupted)。
- 终结器规则:对象的构造函数必须在启动该对象的终结器之前执行完成。
- 传递性:如果操作 A 在操作 B 之前执行,并且操作 B 在操作 C 之前执行,那么操作 A 必须在操作 C 之前执行。
-
三个特性的保障:
- 原子性:原子性变量操作
- 可见性:线程修改了共享变量的值,其他线程可以立即得知这个修改(volatile、synchronized、final)
- 有序性:在本线程内观察,操作有序;在其他线程观察本线程,操作无序(volatile(禁止指令重排序)、synchronized(一个变量同一时刻只允许一个线程对其lock操作))
线程池
-
优点:降低资源消耗、提高响应速度、提高线程的可管理性
-
创建线程池:ThreadPoolExecutor来创建一个线程池
参数:
-
corePoolSize(线程池的基本大小)
-
runnableTaskQueue(任务队列):可选
ArrayBlockingQueue(基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序)
LinkedBlockingQueue(基于链表结构的阻塞队列,此队列按FIFO排序元素)
SynchronousQueue(不存储元素的阻塞队列)
PriorityBlockingQueue(具有优先级的无限阻塞队列)
-
maximumPoolSize(线程池最大数量)
-
ThreadFactory:用于设置创建线程的工厂
-
RejectedExecutionHandler(饱和策略):
·AbortPolicy:直接抛出异常。-----默认
·CallerRunsPolicy:只用调用者所在线程来运行任务。
·DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
·DiscardPolicy:不处理,丢弃掉。也可以实现RejectedExecutionHandler接口自定义策略
-
keepAliveTime(线程活动保持时间)
-
TimeUnit(线程活动保持时间的单位)
-
-
向线程池提交任务:
- execute():提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
- submit():提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完
-
关闭线程池
-
shutdown或shutdownNow
-
原理:遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止
-
区别:
shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
-
-
监控:通过线程池提供的参数进行监控
- taskCount:线程池需要执行的任务数量
- completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount
- largestPoolSize:线程池里曾经创建过的最大线程数量
- getPoolSize:线程池的线程数量,只增不减
- getActiveCount:获取活动的线程数
- 通过扩展线程池进行监控。可以通过继承线程池来自定义线程池,重写线程池的beforeExecute、afterExecute和terminated方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控
-
四种线程池
- CachedThreadPool:创建一个可根据需要创建新线程的线程池
- FixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程
- ScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
- SingleThreadExecutor:返回一个线程池(这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去
-
线程池原理----控制运行的线程的数量
-
线程复用:继承重写Thread 类,在其 start 方法中添加不断循环调用传递过来的 Runnable 对象。 这就是线程池的实现原理。循环方法中不断获取 Runnable 是用 Queue 实现的
-
线程池的组成:
线程池管理器:用于创建并管理线程池
工作线程:线程池中的线程
任务接口:每个任务必须实现的接口,用于工作线程调度其运行
任务队列:用于存放待处理的任务,提供一种缓冲机制
-
拒绝策略:线程用完
-
线程池工作过程:
- 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
- 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
a) 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
b) 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列;
c) 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要
创建非核心线程立刻运行这个任务;
d) 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池
会抛出异常 RejectExecutionException。 - 当一个线程完成任务时,它会从队列中取下一个任务来执行。
- 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
锁
一、JAVA 锁
-
乐观锁
- 在更新的时候会判断一下有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作
- CAS 操作实现
-
悲观锁
- 在读写数据的时候都会上锁
- Synchronized
- AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock
-
自旋锁
- 如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗
- 需要消耗 cup,需要设定一个自旋等待的最大时间
- 优点:对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗
- 缺点:但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,自旋锁占用 cpu 做无用功
- 自旋锁时间阈值:由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定,基本认为一个线程上下文切换的时间是最佳的一个时间;还针对cpu负荷情况优化
-
Synchronized 同步锁
-
把任意一个非 NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重
入锁。 -
作用范围:实例、class实例、代码块
-
Synchronized 核心 组件:
- Wait Set:哪些调用 wait 方法被阻塞的线程被放置在这里;
- Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
- Entry List:Contention List 中那些有资格成为候选资源的线程被移动到 Entry List 中;
- OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为 OnDeck;
- Owner:当前已经获取到所资源的线程被称为 Owner;
- !Owner:当前释放锁的线程。
-
Synchronized 实现
-
-
ReentrantLock
- 继承接口 Lock 并实现了接口中定义的方法,可重入锁
- 非公平锁:JVM 按随机、就近原则分配锁的机制则称为不公平锁
- 公平锁:锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁
-
Semaphore 信号量:基于计数的信号量
-
AtomicInteger:提供原子操作的 Integer 的类
-
可重入锁:,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响
-
ReadWriteLock 读写锁
-
共享锁和独占锁:
-
重量级锁 ( Mutex Lock ):依赖于操作系统 Mutex Lock 所实现的锁
-
轻量级锁
-
锁 优化:减少锁持有时间、减小锁粒度、锁分离、锁粗化、锁消除
-
线程基本方法:wait,notify,notifyAll,sleep,join,yield
-
线程上下文切换:
-
同步锁与死锁
二、JAVA并发包中的锁
- Lock接口:与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁
线程
一、JAVA 线程实现/ 创建 方式
-
继承 Thread 类
- 启动线程的唯一方法就是通过 Thread 类的 start()实例方法。
- start()方法是一个 native 方法,它将启动一个新线程,并执行 run()方法。
-
实现 Runnable 接口
- 自己的类已经 extends 另一个类,就无法直接 extends Thread
-
ExecutorService 、Callable
、Future 有返回值 线程 - 有返回值的任务必须实现 Callable 接口
- 无返回值的任务必须 Runnable 接口
-
基于线程池的方式
ExecutorService threadPool = Executors.newFixedThreadPool(10);
二、 线程 生命周期:
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 - 阻塞(BLOCKED):表示线程阻塞于锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
- 终止(TERMINATED):表示该线程已经执行完毕。
三、终止 线程 4 种方式
- 正常运行结束
- 使用退出标志退出线程:设一个boolean 类型的标志,并通过设置这个标志为 true或 false 来控制 while循环是否退出;使用了一个 Java 关键字 volatile修饰退出标志
- Interrupt 方法结束线程
- 线程处于阻塞状态:当调用线程的 interrupt()方法时,会抛出InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态
- 线程未处于阻塞状态:中断标志就会置 true
- stop 方法终止线程 (线程不安全)
- thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁。
四、 sleep 与 与 wait 区别
-
对sleep()方法属于 Thread 类中的。而 wait()方法属于Object 类中的
-
sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
-
在调用 sleep()方法的过程中,线程不会释放对象锁。
-
而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
五、start 与 与 run 区别
- start()方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码。
- 通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。
- 方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。
六、JAVA 后台线程
- 设置:通过 setDaemon(true)来设置线程为“守护线程”;
- 为用户线程 提供 公共服务,在没有用户线程可服务时会自动离开。
- 垃圾回收线程就是一个经典的守护线程
第四部分 JVM虚拟机
JVM内存区域

- 线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁在 HotspotVM 内
- 线程共享区域随虚拟机的启动/关闭而创建/销毁
一、内存区域
- 程序计数器( 线程私有):是当前线程所执行的字节码的行号指示器,没有规定任何 OutOfMemoryError 情况
- 虚拟机栈( 线程私有):是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息
- 每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
- 本地方法区( 线程私有):本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为Native 方法服务
- 堆(Heap- 线程共享):创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行
垃圾收集的最重要的内存区域- VM 采用分代收集算法: 新生代( Eden 区 、 From Survivor 区 和 To Survivor 区 )和老年代
- 方法区/ 永久代:用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版
本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
- 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版
二、JVM 运行时内存
-
新生代:存放新生的对象。一般占据堆的1/3空间
-
Eden 区:Java新对象的出生地
-
ServivorFrom:上一次 GC 的幸存者,作为这一次 GC 的被扫描者
-
ServivorTo:保留了一次 MinorGC 过程中的幸存者
-
MinorGC 的过程(复制->清空->互换)
eden 、 servicorFrom 复制到 ServicorTo,年龄+1
清空 eden 、 servicorFrom
ServicorTo 和 ServicorFrom 互换
-
-
老年代:存放应用程序中生命周期长的内存对象
- 空间不够用或者无法找到足够大的连续空间分配给新创建的较大对象,触发一次 MajorGC
- 标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象
-
永久代
内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域,它和和存放实例的区域不同,GC 不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异常
-
JAVA8 与元数据:在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代,元空间并不在虚拟机中,而是使用本地内存
垃圾回收算法
一、确定垃圾
- 引用计数法:引用和对象是有关联的。如果要操作对象则必须用引用进行,对象如果没有任何与之关联的引用则可以回收
- 可达性分析:解决引用计数法的循环引用问题;如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的;不可达对象变为可回收对象至少要经过两次标记过程
二、标记清除算法( Mark-Sweep )
分为两个阶段,标注和清除。
- 标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间
- 内存碎片化严重
三、复制算法(copying )
- 解决 Mark-Sweep 算法内存碎片化的缺陷
- 按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉
- 可用内存被压缩到了原本的一半。且存活对象增多的话,Copying 算法的效率会大大降低
四、标记整理算法(Mark-Compact)
- 先标记,与Mark-Sweep 类似
- 然后将存活对象移向内存的一端。然后清除端边界外的对象
五、分代收集算法
- 大部分 JVM 所采用的方法
- 据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代和新生代
- 老生代的特只有少量对象需要被回收;新生代有大量垃圾需要被回收,可以根据不同区域选择不同的算法
- 新生代与复制算法:因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少
- 老年代与标记复制算法:
- 永生代的回收主要包括废弃常量和无用的类
- 当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC
- 如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代
- 默认情况下年龄到达 15 的对象会被移到老生代中
JAVA四种引用类型
一、强引用
- 把一个对象赋给一个引用变量,这个引用变量就是一个强引用。
- 当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收(不用也不能被回收)
- 造成 Java 内存泄漏的主要原因之一
二、软引用
- 用 SoftReference 类来实现
- 当系统内存足够时它不会被回收
三、弱引用
- 用 WeakReference 类来实现
- 只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存
四、虚引用
- PhantomReference 类来实现
- 不能单独使用,必须和引用队列联合使用
- 跟踪对象被垃圾回收的状态
GC 分代收集算法 VS 分区收集算法
一、分代收集算法
- 根据对象存活周期的不同将内存划分为几块, 如 JVM 中的 新生代、老年代、永久代,这样就可以根据各年代特点分别采用最适当的 GC 算法
二、分区收集算法
- 分区算法则将整个堆空间划分为连续的不同小区间, 每个小区间独立使用, 独立回收
- 可以控制一次回收多少个小区间 , 根据目标停顿时间, 每次合理地回收若干个小区间
- 可以减少一次 GC 所产生的停顿
GC 垃圾收集器
一、Serial 垃圾收集器 (单线程、 复制算法 )
- 复制算法
- 使用一个 CPU 或一条线程去完成垃圾收集工作,必须暂停其他所有的工作线程。
- 简单高效,对于限定单个 CPU 环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率
- java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器
二、ParNew 垃圾收集器 (Serial+ 多线程)
- Serial 收集器的多线程版本
- 复制算法
- 暂停所有其他的工作线程
- 默认开启和 CPU 数目相同的线程数
- java虚拟机运行在 Server 模式下新生代的默认垃圾收集器
三、Parallel Scavenge 收集器 (多线程复制算法、高效)
-
复制算法
-
多线程的垃圾收集器
-
关注Thoughput,
CPU 用于运行用户代码的时间/CPU 总消耗时间,
即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
四、Serial Old 收集器 (单线程标记整理算法 )
- 使用标记-整理算法
- 运行在 Client 默认的 java 虚拟机默认的年老代垃圾收集器
五、Parallel Old 收集器 (多线程标记整理算法)
- 使用多线程的标记-整理算法
六、CMS 收集器
-
年老代垃圾收集器
-
主要目标:获取最短垃圾回收停顿时间
-
多线程的标记-清除算法
-
工作机制分为四个阶段
- 初始标记:标记一下 GC Roots 能直接关联的对象,暂停所有的工作线程
- 并发标记:进行 GC Roots 跟踪的过程,,不需要暂停工作线程
- 重新标记:修正在并发标记期间,因用户程序继续运行而导致标记产生变动的对象的标记记录,暂停所有的工作线程
- 并发清除:清除 GC Roots 不可达对象,不需要暂停工作线程
耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。
七、G1 收集器(Garbage first)
- 基于标记-整理算法,不产生内存碎片。
- 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
- 原理:避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域
JAVA IO/NIO
一、阻塞 IO 模型
-
最传统的一种 IO 模型,即在读写数据过程中会发生阻塞现象
-
data = socket.read();
如果数据没有就绪,就会一直阻塞在 read 方法。
二、非阻塞 IO 模型
- 用户线程需要不断地询问内核数据是否就绪
- 不会交出 CPU,而会一直占用 CPU
三、多路复用 IO 模型(Java NIO)
- 有一个线程不断去轮询多个 socket 的状态,只有当 socket 真正有读写事件时,才真正调用实际的 IO 读写操作
- 轮询每个 socket 状态是内核在进行的,这个效率要比用户线程要高的多
- 一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。
四、信号驱动 IO 模型
- 当用户线程发起一个 IO 请求操作,会给对应的 socket 注册一个信号函数,然后用户线程会继续执行
- 当内核数据就绪时会发送一个信号给用户线程
- 用户线程接收到信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。
五、异步 IO 模型
-
最理想的 IO 模型
-
只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,可以直接去使用数据了
-
IO 操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成
七、JAVA NIO
-
三大核心部分:Channel(通道),Buffer(缓冲区), Selector
-
NIO 基于 Channel 和 Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中
-
Selector(选择区)用于监听多个通道的事件
-
IO 是面向流的,NIO 是面向缓冲区的
-
NIO 的缓冲区
- 数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。
- 但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
-
NIO 的非阻塞
NIO 的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取
-
Channel:和Stream类似,但Channel 是双向的,既可以用来进行读操作,又可以用来进行写操作
-
Buffer:缓冲区,实际上是一个容器,是一个连续数组
-
Selector:Selector 能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理
JVM 类加载机制
一、类加载步骤:加载,验证,准备,解析,初始化
- 加载:在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的入口
- 验证:确保 Class 文件的字节流中包含的信息是否符合当 前虚拟机的要求
- 准备:正式为类变量(静态变量)分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间
- 解析:虚拟机将常量池中的符号引用替换为直接引用的过程
- 符号引用:引用的目标并不一定要已经加载到内存中
- 直接引用:引用的目标必定已经在内存中存在
- 初始化:执行类中定义的 Java 程序代码
- 执行类构造器
方法 方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的
- 执行类构造器
二、类加载器:把加载动作放到 JVM 外部实现,以便让应用程序决定如何获取所需的类
- 启动类加载器(Bootstrap ClassLoader)
- 扩展类加载器(Extension ClassLoader)
- 应用程序类加载器(Application ClassLoader)
- 也可以通过继承 java.lang.ClassLoader实现自定义的类加载器。
三、双亲委派
- 当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,只有当父类加载器反馈自己无法完成这个请求的时候,子类加载器才会尝试自己去加载
- 因此所有的加载请求都应该传送到启动类加载其中
- 优点:使用不同的类加载器最终得到的都是同样一个 Object 对象。
- 打破:JDBC、JNDI、Tomcat等
- 重写loadClass():双亲委派机制是通过这个方法实现的,改写加载规则相当于打破机制,可以实现自定义加载委派机制
- 使用线程上下文类加载器:基础类又要调用回用户代码
- 代码热替换(追求动态性)

四、OSGI ( 动态模型系统 )
第五部分 Spring
Spring IOC 原理
一、概念和原理
- Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
二、依赖注入四种方式
- 构造器注入
- setter 方法 注入
- 静态工厂注入
- 实例工厂
Spring APO 原理
一、概念和原理
-
" 横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
-
AOP 核心概念:
1、切面(aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象
2、横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。
3、连接点(joinpoint):被拦截到的点,因为 Spring 只支持方法类型的连接点,所以在 Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器。
4、切入点(pointcut):对连接点进行拦截的定义
5、通知(advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
6、目标对象:代理的目标对象
7、织入(weave):将切面应用到目标对象并导致代理对象创建的过程8、引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
二、AOP 两种代理方式
- JDK 动态接口代理:
- CGLib 动态代理:
Spring Bean
一、作用域
-
singleton单例模式:Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个Bean 引用它,始终指向同一对象、
-
prototype原型模式:每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态
对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。
-
request:在一次 Http 请求中,容器会返回该 Bean 的同一实例。
-
session:在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 请求则会创建新的实例,该 bean 实例仅在当前 Session 内有效。
-
global Session:在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在使用 portlet context 时有效。
二、生命周期
Spring MVC 原理
Spring Boot 原理
JPA 原理
第六部分 设计模式
面向对象设计原则---支持可维护性复用
| 设计原则名称 | 定 义 | 使用频率 |
|---|---|---|
| 单一职责原则****(Single Responsibility Principle, SRP) | 一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类****中 | ★★★★☆ |
| 开闭原则****(Open-Closed Principle, OCP) | 软件实体应当对扩展开放,对修改****关闭 | ★★★★★ |
| 里氏代换原则(Liskov Substitution Principle, LSP) | 所有引用基类的地方必须能透明地使用其子类的****对象 | ★★★★★ |
| 依赖倒转原则****(Dependence Inversion Principle, DIP) | 高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于****抽象 | ★★★★★ |
| 接口隔离原则****(Interface Segregation Principle, ISP) | 客户端不应该依赖那些它不需要的****接口 | ★★☆☆☆ |
| 合成复用原则****(Composite Reuse Principle, CRP) | 优先使用对象组合,而不是继承来达到复用的目的 | ★★★★☆ |
| 迪米特法则****(Law of Demeter, LoD****) | 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件****单位 | ★★★☆☆ |
1.单一职责原则:一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中(就一个类而言,应该仅有一个引起它变化的原因)
2.开闭原则:软件实体应当对扩展开放,对修改关闭
- 软件实体:可以是一个软件模块、一个由多个类组成的局部结构或一个独立的类
- 拓展:软件实体应尽量在不修改原有代码的情况下进行扩展
3.里氏代换原则:所有引用基类的地方必须能透明地使用其子类的对象
- 将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常
- 尽量使用基类类型来对对象进行定义
4.依赖倒转原则:高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象
- 要针对接口编程,不要针对实现编程
- 尽量引用层次高的抽象层类
- 在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中
- 依赖注入
- 构造注入
- 设值注入(Setter注入)
- 接口注入
5.接口隔离原则:客户端不应该依赖那些它不需要的接口
- 分割大接口为细小的接口
- 客户端仅需知道与之相关的方法
- 每一个接口应该承担一种相对独立的角色
- 接口定义
- 一个类型所提供的所有方法特征的集合;代表一个角色
- 狭义的特定语言的接口;定制服务
6.合成复用原则:优先使用对象组合,而不是继承来达到复用的目的
- 关联关系使用已有对象使之成为新对象的一部分
- 委派已有对象的方法
- 少用继承,多用组合/聚合
- 继承缺点太大:三大缺点
- 会继承父类中不需要的方法
- 要继承的类是final类,无法继承
- 修改不方便
- 继承缺点太大:三大缺点
P.S.
1.配置文件+反射:教材49页,自己看书
2.考题
- 结合某设计模式谈谈对某设计原则的理解(结合类图和代码)
- 某设计方案可能违背哪些设计原则
- 设计模式的英文定义
3.开闭原则是目标,里氏替换原则是基础,依赖倒置原则是手段
4.依赖倒置原则中父类尽可能要声明子类中的方法
7.迪米特法则:一个软件实体应当尽可能少地与其他实体发生相互作用
- 尽量减少对象之间的交互
- 引入一个合理的“第三者”来降低现有对象之间的耦合度
- 举例:net->star(QQ聊天群)
设计模式
一、简单工厂模式(静态工厂方法模式)
- 定义一个工厂类,他可以根据参数的不同返回不同的类的实例,被创建的实例通常都具有相同的父类。
1.结构
- Factory(工厂角色):工厂类,负责实现创建所有产品实例的内部逻辑。提供静态工厂方法factoryMethod(),返回抽象产品类型
- Product(抽象产品角色):所有对象的父类,封装公有方法。
- ConcreteProduct(具体产品角色):工厂创建的目标,需要实现抽象方法
2.关于创建对象与使用对象
- new
- 反射机制reflection
- 克隆方法clone/copy
- Factory
- 反序列化
P.S.
1.工厂模式中工厂的静态方法
- 好处:可以直接使用类名调用
- 缺点:不支持多态
2.继承结构->等级结构(名字替换)
3.增加新产品的时候,需要修改工厂源码,违背的开闭原则,同时增加工厂职责,违背单一职责原则
4.解析XML文件
- DOM:存入内存,获取节点
- SAX:逐行读取文件,用于文件过大无法存入内存时
二、工厂方法模式
1.定义:定义一个用于创建对象的接口,但是让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类
2.结构
- Product:抽象产品
- ConcreteProduct:具体产品
- Factory:抽象工厂
- ConcreteFactory:具体工厂
3.反射机制与配置文件
-
反射:程序在运行时获取已知名称的类或已有对象的相关信息
Class c=Class.forName("Class.name"); Object obj=c.newInstance(); return obj; -
配置文件
4.工厂方法的重载
- 添加参数,构造对象
- 在接口中重载方法,具体类中实现
5.工厂方法的隐藏
- 不建议使用,使得代码变得难以理解
6.优点+缺点+适用环境:P54
三、抽象工厂模式
1.产品等级结构:产品的继承结构,抽象电视机与具体品牌的电视机
2.产品族:同一工厂的一组产品
3.定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
4.结构:至少需要十个类
- AbstractFactory:抽象工厂
- ConcreteFactory:具体工厂
- AbstractProduct:抽象产品
- ConcreteProduct:具体产品
5.开闭原则的倾斜性
- 增加产品族时:符合开闭原则
- 增加产品等级结构时:不符合开闭原则
6.优点+缺点+适用模式:P68
三、原型模式
1.定义:使用原型实例指定待创建对象的类型,且通过复制这个原型来创建新的对象
2.三个角色
- Prototype(抽象原型类):声明克隆方法的接口,具体原型类的公共父类
- ConcretePrototype(具体原型类):实现clone方法
- Client(客户类)
3.浅clone与深clone:是否复制包含在原型对象中引用类型的成员变量
- 浅clone不复制,深clone复制
4.原型模式实现
- 通用实现方法:在clone方法中实例化一个自身类型相同的对象并且将其返回,同时将相关参数传入新创建的对象中,保证成员变量相同
- 对于引用类型的对象,使用赋值来实现复制,是浅clone
- 使用创建一个全新的成员对象来实现复制,是深clone
- Java语言中的clone()方法和Cloneable接口
- Java中clone方法满足以下几点
- x.clone()!=x
- x.clone().getClass()==x.getClass()
- 若equals()方法定义恰当,x.clone.equals(x)
- Java中clone方法满足以下几点
5.深clone解决方案
- 通过序列化,将对象写到流中
- 需要实现Serializable接口
6.原型管理器
- 将多个原型对象储存在一个集合中供客户端使用,专门负责克隆对象的工厂
7.优点+缺点+使用环境:P102
四、单例模式
1.定义:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例
2.结构:只包含一个单例角色Singleton,一个静态方法getInstance()获取唯一实例
3.实现:
- 存在一个静态成员变量和静态公有的工厂方法,工厂方法负责检验实例的存在并实例化自己,存储在静态成员变量中,确保只有一个实例被创建
- 注意
- 构造函数为private
- 提供一个类型为自身的静态私有成员变量
- 提供一个公有的静态工厂方法
4.存在的问题:在多线程下可能导致实例不唯一(某个线程创建到一半,另一个线程也开始创建)
5.解决方案
- 饿汉式单例:在定义静态变量的时候实例化单例类
- 类被加载的时候,成员变量被初始化,唯一实例被创建
- 优点:不需要考虑线程安全
- 缺点:类加载的时候耗费时间长;这个实例及时不被使用,也会被创建并一直存在,占用内存
- 懒汉式单例:在第一次被引用的时候将自己实例化(延迟加载)
- 实现一:使用synchronized关键字对getInstance()方法上锁(高并发下会导致系统性能降低)
- 实现二:使用synchronized对创建实例的代码上锁(相当于没锁)
- 实现三:双重检查锁定,对创建实例代码锁定的同时,再加一行检查代码(需要在静态成员变量的前面加上volatile关键词,取消虚拟机对于代码的优化--重复检查代码)
- Java中--静态内部类IoDH:在单例类中添加一个静态内部类
- Java中静态内部类的静态对象加载时,Java虚拟机会保证线程安全
- 类加载时不会分配内存
6.优点+缺点+适用环境:P115
五、适配器模式
1.定义:将一个类的接口转换成客户希望的另一个接口,适配器模式让那些接口不兼容的类可以一起工作
2.结构
- Target-目标抽象类:客户所需的接口
- Adapter-适配器类:转换器,产生联系
- Adaptee-适配者类:希望使用的接口
3.实现
- 类适配器:适配器实现Target接口,与适配者是继承关系
- 对象适配器:适配器继承Target接口,与适配者是关联关系
4.缺省适配器模式
-
定义:当不需要实现一个接口中提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中的每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性的覆盖父类的某些方法来实现需求。
-
适用于不想使用一个接口中所有方法的情况,又称为单接口适配器模式
-
结构:
- ServiceInterface-适配者接口:接口,有大量方法
- AbstractServiceClass-缺省适配器类:使用空方法实现
- ConcreteServiceClass-具体业务类:子类,选择覆盖方法
5.双向适配器:Target和Adaptee可以互换角色
6.优点+缺点+适用环境:P129
P.S.判断类模式和对象模式:关联关系还是继承关系
六、桥接模式(毛笔和蜡笔)
1.定义:将抽象部分(粒度大)与他的实现部分(粒度小)解耦,使得两者都能够独立变化
2.结构:
- Abstraction-抽象类:
- RefinedAbstraction-扩充抽象类:
- Implementor-实现类接口:
- ConcreteImplementor-具体实现类:
3.实现:毛笔和蜡笔
4.桥接模式和适配器模式的联用
5.优点+缺点+适用环境:P144
七、组合模式(文件与文件夹)
1.定义:组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象
2.结构
- Component-抽象构件:接口或者抽象类
- Leaf-叶子构件:相当于文件
- Composite-容器构件:相当于文件夹
3.实现
- 透明组合模式:Component中声明所有管理成员对象的方法,不安全(文件不能拥有文件夹的方法)
- 安全组合模式:Component中不声明管理成员对象的方法,在Composite中声明并实现
4.优点+缺点+适用环境:P158
八、外观模式(茶馆喝茶)
1.定义:为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易被使用。(构建了一个茶馆服务员)
2.结构
- Facade-外观角色:茶馆服务员
- SubSystem-子系统角色:需要的茶具,开水之类
3.实现
- 普通外观模式在实现过程中由于没有抽象层,在需要调用新的子系统的时候,需要修改源代码,违背开闭原则
- 解决---抽象外观类
- 可以与单例模式联用:控制系统资源
4.优点+缺点+适用环境:P185
九、代理模式(代购)
1.定义:给某一个对象提供一个代理或者占位符,并由代理对象来控制对源对象的访问
2.结构
- Subject-抽象主题角色:真实主题和代理主题的共同接口,Client对于它进行编程,代购行业
- Proxy-代理主题角色:代购人
- RealSubject-真实主题角色:代购商品
3.实现
- 远程代理:为一个位于不同地址空间中的对象提供一个本地的代理(大使)
- 虚拟代理:使用资源消耗消耗相对较小的对象表示资源消耗较大的对象(桌面快捷方式)
- 保护代理:控制对一个对象的访问,提供不同级别的使用使用权限
- 缓冲代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果(内存->硬盘 空间换时间)
- 智能引用代理:提供一些额外的操作
- Java动态代理:在系统运行时根据需要动态创建代理类
4.优点+缺点+适用环境:P217
十、装饰模式
1.定义:动态的给一个对象增加一些额外的职责,就扩展功能而言,装饰模式提供了一种比较用子类更加灵活的代替方案
2.实例:awt中的JList与JTable没有滚动条,所以需要作为参数传递给JScrollPane构造函数,来实现滚动条的效果
3.结构
- Component-抽象构件:共同父类
- ConcreteComponent-具体构件:需要使用的构件
- Decorator-抽象装饰类:装饰类的抽象层
- ConcreteDecorator-具体装饰类:负责给构件添加新的职责
十一、职责链模式(向学校请假)
1.定义:避免将一个请求的发送者与接受者耦合在一起,让多个对象独有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止
2.结构
- Handler-抽象处理者:处理者的抽象层,面向该类编程,定义一个抽象处理者类型的对象,声明处理方法
- ConcreteHandler-具体处理者:实现处理方法
3.实现
- 纯职责链模式:每个请求一定被处理,且只被一个处理者处理
- 不纯职责链模式:每个处理者可以只处理请求的一部分,请求也可以不被处理
- 权限小的处理者要在链的前面
- 链在客户端中定义----建链
4.优点+缺点+适用环境:P233
十二、命令模式(开关)
1.定义:将一个请求封装为有一个对象,从而可用不同的请求对客户进行参数化,对请求排队或者记录请求日志,以及支持可撤销的操作
2.结构
- Command-抽象命令类:声明execute()方法,可用调用请求接收者的方法
- ConcreteCommand-具体命令类:实现方法,调用具体请求接收者
- Invoker-调用者:请求发送者
- Receiver-接收者:执行与请求相关的操作
3.实现
- 命令队列
- 记录请求日志
- 撤销操作
- 宏命令
十三、观察者模式
1.定义:定义对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时其相关依赖对象皆得到通知并被自动更新
2.结构:
- Subject-目标:被观察的对象
- ConcreteSubject-具体目标:
- Observer-观察者:观察目标的改变作出反应
- ConcreteObserver-具体观察者:
3.实现
4.优点、缺点、使用环境
十四、模板方法模式---类行为型模式(流程)
1.定义:定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
2.结构:
- AbstractClass-抽象类:定义一系列基本操作、步骤,实现相同方法
- ConcreteClass-具体子类:特定实现不同的步骤
3.实现
- 模板方法:组合一系列步骤
- 基本方法:实现各个步骤
- 抽象方法
- 具体方法
- 钩子方法:一般返回bool类型,判断是否执行某个步骤
十五、策略模式
1.定义:定义一系列算法,将每一个算法封装起来,并让他们可以相互替换。策略模式让算法可以独立于使用它的客户而变化
2.结构:
- Context-环境类:使用算法的角色
- Strategy-抽象策略类:声明算法
- ConcreteStrategy-具体策略类:实现算法
更多文章请关注《万象专栏》
转载请注明出处:https://www.wanxiangsucai.com/read/cv129532