01 基本设计
1 数据类型
- 自动类型转换
- +、-、*、/、%:转换为较大的数据类型,如最大的小于int,则转为int
- 强制类型转换
- +=、-=、*=、/=、%=:右边的数值被强制转换成左边的数据类型,然后再执行运算
- 整型常量池:如果整型字面量的值在 -128 到 127 之间,直接引用常量池中的 Integer 对象,不会 new 新的 Integer 对象
2 控制流程
- switch 语句
- Java 7 以前,只支持 byte、short、char、int、enum 类型
- Java 7 开始,支持 String 类型(内部实现是使用字符串的 hash code)。
- 不支持长整型(long)
02 对象与类
1 面向对象
- 多态:一个对象变量可以指示多种实际类型的现象
- 必要条件:继承、重写、上溯造型
- 动态绑定、静态绑定
2 修饰符
- final(域、方法、类)
- static(域、常量、方法、内部类、代码块、导包)
3 类
- 构造器:若子类构造器没有显式调用父类构造器,默认调用父类无参构造器。
- 创建对象
- new 语句
- 反射
- clone() 方法
- 反序列化
- 对象克隆
- 浅克隆:仅复制所拷贝的对象,而不复制它所引用的对象
- 深克隆:把要复制的对象所引用的对象都复制了一遍
- 实现 Cloneable 接口并重写 clone() 方法;
- 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆
- 序列化 id:反序列化时做校验,默认根据类摘要自动生成,可指定
- 方法参数:值传递
- 内部类
- 局部内部类:在方法中定义,对外界完全隐藏
- 匿名内部类:只创建一个这个类的对象,主要用在回调,如今推荐用 lambda
- 静态内部类:仅为隐藏在另一个类的内部,并不需要内部类引用外围类对象
4 接口
- 与抽象类区别:接口没有实例域,但可以实现多个接口。
- 不能含有实例域
- JDK 8 之前,不能实现方法。
- JDK 8 之后,允许提供静态方法、默认方法(default标记)
5 泛型
- 泛型类
- 类名的后面 + 类型变量(
) - 泛型类可看作普通类的工厂。
- 类名的后面 + 类型变量(
- 泛型方法
- 修饰符的后面 + 类型变量(
) + 返回类型的前面 - 泛型方法可以定义在普通类中,也可以定义在泛型类中。
- 修饰符的后面 + 类型变量(
- 类型变量的限定
- 限定类型用“&”分隔,而逗号用来分隔类型变量。
- <T extends Comparable & Serializable,U>
- 限定中至多有一个类,且若有,则必须是限定列表中的第一个。
- 通配符类型
- 子类型限定:<? extends Class>
- 超类型限定:<? super Manager>
- 无限定通配符:<?>
- 泛型代码和虚拟机
- 类型擦除:虚拟机中没有泛型,泛型类型都会转换为为第一个限定类型(无限定的变量用Object)
- 类型转换:当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。
- 桥方法:当泛型方法被擦除后与多态发生冲突,编译器会在泛型方法类中生成一个桥方法。
6 反射
- 反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,忽略权限检查。
- 如果不需要动态地创建一个对象,那么就不需要用反射。
7 代理
代理可以在不改动目标对象的基础上,增加其他额外的功能(扩展功能)。
- 静态代理
- 代理对象和目标对象实现了相同的接口。
- 代理对象可以在调用目标对象相应方法时加上增强处理的逻辑。
- 动态代理
- 代理对象不需要实现接口,目标对象要实现接口
- 通过反射,重写 Invoke 方法,代理对象方法 = 增强处理 + 被代理对象的方法
- Cglib 代理(子类代理)
- Cglib 代理针对类,是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
- 在 Spring 的 AOP 编程中:
- 如果加入容器的目标对象有实现接口,用 JDK 代理;
- 如果目标对象没有实现接口,用 Cglib 代理。
03 集合
1 集合接口
1)List
- ArrayList:数组,默认容量 10,扩容 1.5 倍新数组
- LinkedList:双向链表,同时实现了 Deque 接口
2)Deque
- ArrayDeque:循环数组,默认容量 16,扩容 2 倍新数组
- 栈和队列首选
- PriorityQueue:数组,默认容量 11,扩容(原容量 64 以下 2 倍,之后 1.5 倍)新数组
- 优先队列:是能保证每次取出的元素都是队列中权值最小的。
- 小顶堆:插入时在数组末尾添加,并调整;删除时,将最末尾的元素移动到 0 下标,并调整
3)Map(Set)
- HashMap(HashSet):数组 + 链表,默认容量 16,负载因子 0.75,扩容 2 倍新数组,头插法
- JDK 1.8 优化查询效率:链表数超过(默认)8,则转为红黑树,降到(默认)6,恢复为链表
- TreeMap(TreeSet):红黑树;自然顺序。
- LinkedHashMap(LinkedHashSet):双向链表;(默认)插入顺序,(可选)访问(频率少多)顺序。
- WeakHashMap:弱引用
- EnumMap(EnumSet):键值属于枚举类型
- IdentityHashMap:用 == 比较键值
2 同步的集合
- Vector(过时):同步的 ArrayList,单锁,效率低(synchronized 所有方法)
- Hashtable(过时):同步的 HashMap,单锁,效率低(synchronized 所有方法)
- ConcurrentHashMap(ConcurrentHashSet):同步的 HashMap,推荐
- JDK 1.7:Segment(ReentrantLock)分段锁 + (volatile)HashMap,get 不需锁
- JDK 1.8:抛弃 Segment,直接(volatile)HashMap,并 CAS + synchronized(数组一项),链表超长转红黑树
- ConcurrentSkipListMap(ConcurrentSkipListSet):跳表,代替平衡树
- ConcurrentLinkedQueue:非阻塞的链表
- PriorityBlockingQueue:同步的,阻塞的,优先队列
- CopyOnWriteArrayList(CopyOnWriteArraySet):写时复制,读多写少时替代 ArrayList
- 写元素时先复制一个新容器,往新容器里写,然后将原容器引用指向新容器
- 无锁并发读,只保证最终一致性
- Collections.synchronizedList(synchronizedMap、synchronizedSet)方法:任何集合类通过使用同步包装器变成线程安全的
3 不允许 null 元素
- ArrayDeque、PriorityQueue
- Hashtable、ConcurrentHashMap(ConcurrentHashSet)
4 遗留的集合
- 枚举(Enumeration 接口)
- 属性映射表(property map):Properties 类
- 栈:Stack 类,被 Deque 取代。
- 位集:BitSet 类,位数组,将位包装在字节里
04 并发
1 线程
1)基本概念
- 并发:单核多个任务
- 并行:多核多个任务
- 进程:程序,有独立的代码和数据空间
- 线程:任务,同一类线程共享代码和数据空间
2)实现多线程
调用 start 方法启动新线程,run 方法执行同一个线程
- 继承 Thread 类,创建 Thread 对象
- 实现 Runnable 接口,创建 Thread 对象
- 实现 Callable 接口,(创建 FutureTask 对象)创建 Thread 对象
- Callable 可返回 Future,Future 保存异步计算的结果。
3)线程状态
New、Runable、(Blocked、Waiting、Timed Waiting)、Terminated
4)线程池
执行器(Executor)类有许多静态工厂方法用来构建线程池。
- 线程池管理器(ThreadPoolManager):用于创建并管理线程池
- 基本大小:没有任务执行时线程池的大小,工作队列满了才能创建更多线程
- 最大大小:可同时活动的线程数量的上限
- 存活时间:空闲时间超过了存活时间的线程可被回收,在线程池超过了基本大小时此线程被终止。
- 工作线程(WorkThread):线程池中线程
- 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
- 任务队列:用于存放没有处理的任务。提供一种缓冲机制。
- 无界队列:无界的 LinkedBlockingQueue
- 有界队列:需选择饱和策略。ArrayBlockingQueue、有界的 LinkedBlockingQueue、PriorityBlockingQueue。
- 饱和策略:中止、抛弃、抛弃最旧的、调用者运行
- 同步移交:任务不排队,直接从生产者移交给工作者线程。SynchronousQueue
2 同步
1)锁
- 可重入锁(ReentrantLock)
- 持有计数,线程可重复获得已经持有的锁
- 高度控制,更“危险”,手动 unlock
- 条件对象(Condition):管理已经获锁但还不能运行的线程
- 内置锁(synchronized)
- 每一个对象都有一个内部锁,只有一个条件对象
- 自动 unlock
- 读写锁(ReentrantReadWriteLock )
- 对读线程共享,写线程互斥
- 可选公平锁、非公平锁(默认)
死锁
一组线程,其中每个都在等待一个只有其它线程才可以执行的操作
- 死锁的必要条件
- 互斥:一个资源每次只能被一个线程使用
- 占有并等待:一个进程因请求资源而阻塞时, 对已获得的资源保持不放
- 非抢占:线程已获得的资源, 在未使用完之前, 不能强行被剥夺
- 循环等待:若干进程之间形成一种头尾相接的循环等待资源关系
- 避免死锁
- 以相同顺序获取锁
- 使获取多个锁的集合尽量小
- 尽可能地使用开放调用(在调用某个方法时不需要持有锁)
- 使用定时锁(tryLock 方法):试图申请一个锁,成功返回 true,否则立即返回 false,避免阻塞
锁优化
- 缩小锁范围:尽可能缩短锁的持有时间,将与锁无关的代码移除同步代码块
- 减小锁粒度:通过“锁分解”、“锁分段”,降低每个锁被请求的频率。
- 锁粗化:在一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中时
- 自旋锁:让后面请求锁的线程等待,while 轮询
- 自适应的自旋锁:自旋时间由同一锁前次的自旋时间及锁的拥有者的状态来决定
- 锁消除:即时编译时对不可能存在共享数据竞争的锁进行消除
- 轻量级锁:在无竞争的情况下,使用 CAS,如有两条以上的线程争用,要膨胀为重量级锁
- 偏向锁:在无竞争的情况下把整个同步都消除掉,无需 CAS;当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束
2)同步器
如果同步器能满足需求,就不要试图提供手工的锁与条件的集合。
- 信号量(Semaphore)
- 限制访问资源的线程总数。设一个许可数,线程获取或释放一个许可。
- 闭锁(CountDownLatch)
- 类似计数器,让线程集等待,直到计数变为 0。计数为 0 后不能重用。
- 栅栏(CyclicBarrier)
- 可重用计数器,让线程集等待至某个状态之后再全部同时执行。
- 交换器(Exchanger)
- 同步点。两个线程运行到 Exchanger 时,等待并交换数据。
- 同步队列(SynchronousQueue)
- 将生产者与消费者线程配对。当一个线程调用 put 方法时,会阻塞直到另一个线程调用 take 方法为止,反之亦然。
3)非阻塞同步
- Volatile:只保证可见性,禁止指令重排序优化,不保证原子性。
- 原子变量:基于 CAS,更好的 volatile。中低强度的竞争下,性能优于锁。
- CAS(Compare and Swap):内存值 V、旧预期值 A、要修改的值 B,仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true,否则继续循环获取最新的 value 并进行 CAS,直至获取的 value 和内存中的 value 一致为止。
无同步方案
- 不变性:final 域
- 无状态对象:不包含域,也不包含与其他类中域的引用。
- 栈封闭:局部变量
- 线程封闭:ThreadLocal
- 每个 Thread 维护一个 ThreadLocalMap 映射表,key 是 ThreadLocal 实例, value 是真正需要存储的 Object 。
- 防止内存泄漏:ThreadLocalMap 中的 key 使用了弱引用,进行 get 之前,必须先 set
05 Java 内存模型
1 主内存与工作内存
- 主内存:所有的变量都存储在主内存中,线程不能直接读写主内存中的变量
- 工作内存:线程有自己的工作内存,线程在工作内存中操作主内存变量的副本拷贝
- 内存栅栏:从本地或工作内存到主存之间的拷贝动作
2 先行发生原则
如果说操作 A 发生操作 B 之前,操作 A 产生的影响能被操作 B 观察到,影响包括了修改了内存中共享变量的值、发送了消息、调用了方法等
Java 内存模型下一些天然的先行发生关系:程序次序规则、管程锁定规则、volatile变量规则、线程启动规则、线程终止规则、线程中断规则、对象终结规则、传递性;
时间先后顺序与先行发生原则之间基本没有太大的关系