JAVA 基础
dubbo 系列 https://www.jianshu.com/p/f06d62fd1a73
小会推荐肯定涨姿势 http://mp.weixin.qq.com/s/0JVy3W9uXwUJZA5_tTtrsw
1. hashMap 结构
数组每个元素是一个list,当list的元素达到某个阀值(默认8)变成 rd-tree, 当数组的填充 率超过某个阀值(默认75%)进行扩容 << 1
2, rd-tree
平衡树的一个变种
普通的平衡数,大多都是利用高度(决定了最坏复杂度) 来作为平衡因子的,这样的树,看起来更加的平衡,但是!这种平衡带来了查询相比较快,而旋转的次数相对比较多,进而才有了红黑树.
而红黑树引用了节点颜色的标记维护了特性
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
从而较少了旋转,但是结构也满平衡. ps.还有一种叫伸展树的结构他能 维护 二八原则,输入法.(不说了没用到过)
3.ConcurrentHashMap
结构类似HashMap. 是线程 安全的,唯一不用的是 利用的是 Sychronized锁Node CAS插入链表 超过阀值 变RD-TREE,同时在扩容时,可能会有多个线程一起同步拷贝扩容.
有人会问 size(), 都是 volatile 修饰的,维护了每个位置的size. 具有 可见性
4. topN 问题.
(1): 利用堆来维护topN(对原数组没影响) ->[N小,数据大到内存,存不下]
(2):利用快排的思想查找topN (会改变这个数组的位置) -> [N大,存的下]
5.Thread的状态
NEW 状态是指线程刚创建, 尚未启动
RUNNABLE 状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等
WAITING 这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束
TIMED_WAITING 这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态
TERMINATED 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)
6. 说一说volatile
java关键字之一
(1)他所修饰的变量具有可见性,线程修改这个值都会同步到主存,(因为当前的CPU具有多级缓存), 典型的应用如 单列模式的 double check
(2)内存屏障,防止指令重排, 如下代码
public void writer(int value) {
a = value; //1 flag = true; //2
} public void reader() { if (flag) { // 3 int ans = a; // 4 } }
flag没有volatile修饰, function writer 的顺序可能变成2 -> 1 , (由于多线程,或者CPU轮转 使 step4 先于step1), reader 的 ans值 变得错误.
a的没有volatile修饰, (两个方法又是 两个 不同CPU核 去调用) 且 这个CPU缓存了a, 那么没有读取主存,而导致错误.
7. ReentrantLock
可重入锁,父类是Lock
其内部主要实现了 (Sync extends AbstractQueuedSynchronizer, final class NonfairSync extends Sync, final class FairSync extends Sync),其中的AOS下面会说.
默认的是非公平锁,
非公平锁
(1). CAS当前有人占有么? 可以认为是 插队操作,也可能是 因为后边就没有任务
(2)然后就是 根据 是 公平还啊时 非公平,在 获取一次(自旋锁),
看下 当前是线程是不是 owner等(可重入锁),
失败后就会挂起等待唤醒(重锁),
而被 头节点唤醒后就又去 获取,因为 存在插队,所以还可能会被失败,而又 等待唤醒.
8.Sychronized
同为java关键字,做作用Object和object的方法.是一直线程安全的保障.而且是一个细粒 的锁 参考ReentrantLock.
原理:Object头会有monitor, 进入和 退出 是 monitor的enter和 exit.
Ps.这个 关键字在修饰static方法时获取的Class对象的monitor.
而且其往往 和 Object.wait, Object.notity, 来实现生产消费.
9.Coodition
简单的说 Sychronized 和 Object.notity, wait notify all 是 联合使用,
interface Lock, Coodition 是联合使用 ,但是Coodition更 细, 因为一个 Lock可以有多个coodition, 而 Coodition a的 signal只能 唤醒a 的await;
10.阻塞队列
(1)LinkedBlockingQueue : 双向链表 因为 利用 原子性的 AtomicInteger count 记录变量数量,
所以访问是 1-int.max,两个锁,两个信号量.
(2)ArrayBlockingQueue : 循环数组 需要传递一个 <int capacity, boolean fair = false, Collection> 同样是 一个锁,两个信号量, 只要有操作 就 加锁.
(3)DelayQueue: 是线程安全的优先级队列.一个锁一个信号量.
11.线程池.
核心类 ThreadPoolExecutor
/** * 1. 通常通过Executors {factory methods} 构造 * 2. 如果提交的时候running的线程小于corePoolSize那么 会直接运行 * 3. 如果running的线程>corePoolSize && < maximumPoolSize && queue满了 那么也会运行这个提交 * 4. corePoolSize,maximumPoolSize 可以动态设定 * 5. 核心线程被初始化在第一个任务到达,或者通过[prestartCoreThread, prestartAllCoreThreads],当然你也可以传递一个非空的工作队列,那他也会跑. * 6. 这里面执行的线程都是通过 ThreadFactory (有默认值)创建的(这里面故事挺多的有兴趣的自己看) * 7. 对于当前线程已经超过了corePoolSize的情况下,多余的线程在空闲超过keepAliveTime的情况下才会被释放. * 8. 补充7,allowCoreThreadTimeOut核心线程的回收可设置 * * 9. 队列以及执行 1)小于核心线程数,新任务到达,不管有没有队列,都是直接新建一个核心线程。 2)如果大于等于核心线程数量时在运行,会入队,如不下队列就直接新建线程,如果线程已经达到最大那么就 拒绝 a) SynchronousQueue 直接队列 一个put 一个take,直到 线程MAX b) LinkedBlockingQueue 无界队列 不会有入不了队的情况 c) ArrayBlockingQueue 有界队列 队列有个阀值,入不了对会触发上述的动作. * 10. 拒绝: 1) Executor 停止了 2) 在 有界队列 的情况下队满且线程MAX * 11. 拒绝策略:(RejectedExecutionHandler) 1)AbortPolicy, 抛出运行时异常 2)CallerRunsPolicy 调用者直接运行,不在线程中运行。 3)DiscardPolicy 直接将任务丢弃 4)DiscardOldestPolicy 丢弃队列中头部的任务。 **/
工厂类Executors常用的 SingleThreadPoll, FixedThreadPool(只有核心), newCachedThreadPool(没有核心, 只有可回收线程60S) 都是 ThreadPoolExecutor实现
ScheduledThreadPoolExecutor 是 包装了个delayQueue,的ThreadPoolExecutor
WorkStealingPool 是一个ForkJoinPool(没怎么用过 不大熟悉).
12.ThreadLocal
ThreadLocal类用来提供线程内部的局部变量。 主要是获取是 Thread里的 threadLocals 变量.是弱引用
13.CLH 和 AQS
CLH加锁
1. 创建一个的需要获取锁的 Node
2. 通过 CAS操作 让自己 成为这个尾部的节点,然后令 设置自己的pre
3. 自旋,直到pre节点释放
释放:
1. 标记自己的状态为释放.
java内AQS也叫 AbstractQueuedSynchronizer 是一个 抽象的同步队列.是的他是一个抽象类,他的子类要么用它实现了共享锁,要么实现了互斥锁.
也因此,在他的主类里 并没有实现 protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } 这个方法, 延迟到子类.以实现区别.
简单的说AQS 通过CAS维护 一个 <volatitle int state, exclusiveOwnerThread> 在表示目前是否有线程,以及哪个线程获取到了锁.
具体的入队,出队.代码分析我写在这里 http://www.cnblogs.com/shuly/p/7235997.html
14. CAS的 ABA问题
带上一个递增的版本号.
解决请参考package java.util.concurrent.atomic.AtomicStampedReference
15.JMM
程序计数器:决定了程序的运行逻辑
本地方法栈:native方法运行的函数调用栈存储着.
栈 :我们编写的java程序的函数调用栈.
以上是 每个线程都会分配的下面 是共享的
方法区 : 存储了每个类 的 编译信息(名称, 方法, 属性),常量等. 以前叫 永久带,不过oracle在1.8后叫元空间了,具体在那里实现分看JVM
堆 :这个空间我们很熟悉 (分为 新生代,老年代 默认比例1:2) ,YGC频繁,老年代就很少,
新生代 分为 eden,S1,S2, 默认 比例是8:1:1.
16.GC
确定垃圾:引用技数,会有AB抱团问题. 因此都是采用可达性方法.GCroot
1.虚拟机栈中引用的对象(本地变量表)
2.方法区中静态属性引用的对象
3. 方法区中常量引用的对象
4.本地方法栈中引用的对象(Native对象)
方法:
标记清除(mark-sweep): 标记需要回收的,然后回收 -> 有碎片
复制(copying) : 内存 分相等两块 一块用完复制到另一块,然后清除这一块 ->可用内存 减半
标记整理(Mark-Compact) : 和 标记清楚差不多,多了移动操作 去除碎片.
分代 : 新生代朝生暮死 死的 贼快, 老年代, 一次回收少量.也因此新生代采用的是 copying, 而老年代采用 标记整理.
GC失败会样?
17:典型的垃圾收集器
(1)Serial Y: 复制(Copying) O:标记整理 ; 单线程
(2)ParNew:Serial收集器的多线程版本,使用多个线程进行垃圾收集。
-------------------------------------------------
(3)Parallel :器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,该收集器与前两个收集器有所不同,它主要是为了达到一个可控的吞吐量。
(4)Parallel old: Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法.
(5)CMS : 收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。(会有碎片)
(6)GarbageFirst(G1):
G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,
它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。