线程池的核心参数
corePoolSize 核心线程数目
最多保留的线程数
maximumPoolSize最大线程数目
核心线程 + 救急线程
KeepAliveTime生存时间
针对救急线程
unit时间单位
针对救急线程
workQueue
阻塞队列
threadFactory线程工厂
可以为线程创建时起个好名字
handler拒绝策略
- AbortPolicy 抛异常策略
- CallerRunsPolicy 线程池已经满了,让调用者去执行
- DiscardPolicy 把任务丢弃
- DiscardOldestPolicy 丢弃最先进入队列的任务,新任务加入队列中
sleep vs wait
共同点
wait(),wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态
方法归属不同
- sleep(long)是Thread静态方法
- 而wait(),wait(long)都是Object的成员方法,每个对象都有
醒来时机不同
- 执行sleep(long) 和 wait(long)的线程都会在等待相应毫秒醒来
- wait(long)和wait() 还可以被notify唤醒,wait()如果不唤醒就会一直等待下去
- 他们都可以被打断唤醒
锁特性不同
- wait方法的调用必须先获取wait对象的锁,而sleep则无需
- wait方法执行后会释放对象锁,允许其他线程获得该对象锁(我弃用,你们可以用)
- 而sleep如果在synchorized代码块中执行,并不会释放对象锁(我放弃,你们也不能用)
lock vs synchronized
语法层面
- synchronized 是关键字,源码在jvm中,用c++语言实现
- Lock是接口,源码由jdk提供,用java语言实现
- 使用synchronized是,退出同步代码块锁会自动释放,而使用Lock时,需要手动调用unlock方法释放锁
功能层面
- 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
- Lock提供了许多synchronized 不具备的功能,例如获得等待状态,公平锁,可打断,可超时,多条件变量
- Lock有适合不同场景的实现,如ReentrantLock,ReentrantReadWriteLock
性能层面
- 在没有竞争时,synchronized做了很多优化,如偏向锁,轻量级锁,性能不赖
- 在竞争激烈时,Lock的实现通常会提供更好的性能
volatile能否保证线程安全
线程安全要考虑三个方面:可见性,有序性,原子性
- 可见性指,一个线程对共享变量修改,另一个线程能看到最新的结果
- 有序性指,一个线程内代码按编写顺序执行
- 原子性指,一个线程内多行代码以一个整体运行,期间不能有其他线程的代码插队
volatile能够保证共享变量的可见性与有限性,但并不能保证原子性
java中的悲观锁与乐观锁
1.悲观锁的代表是synchronized和Lock锁
- 其核心思想是(线程只有占有了锁,才能去操作共享变量,每次只有一个线程占锁成功,获取失败锁的线程,都得停下来等待)
- 线程从运行到阻塞,再从阻塞到唤醒,涉及线程上下文切换,如果频繁发生,影响性能
- 实际上,线程在获取synchronized 和 Lock 锁时,如果锁已被占用,都会做几次重试操作,减少阻塞的机会
乐观锁的代表是AtomicInteger,使用cas来保证原子性
- 其核心思想是(无需加锁,每次只有一个线程能成功修改共享变量,其它失败的线程不需要停止,不断重试直到成功)
- 由于线程一直运行,不需要阻塞,因此不涉及线程上下文切换
- 他需要多核cpu支持,且线程数不应超过cpu核数
Hashtable vs ConcurrentHashMap
相同点:键值不能为空
- Hashtable 与 ConcurrentHashMap 都是线程安全的Map集合
- Hashtable并发度低,整个Hashtable对应一把锁,同一时刻,只能有一个线程操作它
- 1.8之前ConcurrentHashMap使用了Segment + 数组 + 链表的结构,每个Segment对应一把锁,如果多个线程访问不同的Segment,则不会冲突
- 1.8开始ConcurrentHashMap将数组的每个头节点作为锁,如果多个线程访问的头节点不同,则不会冲突
Hashtable的小细节
- 它的初始化容量是质数
- 它的扩容是原容量*2 + 1
- 它是不需要二次hash的,求索引是hash值%容量
###1.7ConcurrentHashMap
- 它有三个参数:capacity(容量),factor(扩容因子),clevel(并发度)
- 它的数组容量不是根据capacity来的,而是capacity 除以 clevel,其中的最小容量是2
计算Segment和元素放置的位置
- 计算hash值,然后二次hash,
- 再看的并发度是的2的几次方,
- 去二次hash的二进制高位n位,转换成十进制
- 就是这个值在Segment下了
元素放置的位置
- 看数组容量是2的几次方
- 然后取二次hash的二进制低位n
- 就是元素放置在哪个位置下
Segment[0],这是细节,注意看
- 首先他只会给Segment[0]初始化数组容量
- 然后给其他Segment的添值的时候,则按着Segment[0]的的大小来
- 就是妥妥的设计模式中的原型模式
1.7和1.8的ConcurrentHashMap
在数据结构上
- 1.7是Segment + 数组 + 链表
- 1.8是 数组 + 链表 | 红黑树
###在初始化时机上
- 1.7 是饿汉式
- 1.8 是懒汉式
在扩容时机上
- 1.7 大于阈值就扩容
- 1.8 是等于阈值就扩容
1.8ConcurrentHashMap中的capacity和factor
- capacity 是你要放多少元素
- 然后根据othercapacity * factor
- 是否大于capacity,大于就扩容othercapacity
- factor也就用于一次,之后判断就是othercapacity * 0.75
1.8的ConcurrentHashMap的扩容机制
- 先从后面开始扫,没有元素,则标记
- 有,则在新的数据计算索引,并迁移到新数组中,注意,这里的迁移是复制新的元素,与旧数组元素不一样.
谈一谈对ThreadLocal的理解
- ThreadLocal可以实现(资源对象)的线程隔离,让每个线程各用各的(资源对象),避免争用引发的线程安全问题
- ThreadLocal同时实现了线程内的资源共享
- 其原理是,每个线程内有一个ThreadLocalMap类型的成员变量,用来存储资源对象
调用set方法,就是以ThreadLocal自己作为key,资源对象作为value,放入当前线程的ThreadLocalMap集合中
调用get方法,就是以ThreadLocal自己作为key,到当前线程中查找关联的资源值
调用remove方法,就是以ThreadLocal自己作为key,移除当前线程关联的资源值
ThreadLocal扩容机制
- 等于floor【容量* 2/3】
- 采用是开放寻址法
为什么ThreadLocalMap中的key(即ThreadLocal)要设计为弱引用?
- Thread可能需要长时间运行(如线程池中的线程),如果key不再使用,需要在内存不足(GC)时释放其占用的内存
- 但GC仅是让key的内存释放,后续还要根据key是否为null来进一步释放值得内存,释放时机有
a) 获取key发现null key
b) set key时,会使用启发式扫描,清除临近的nullkey,启发次数与元素个数,是否发现null key有关
c) remove时(推荐),因为一般使用ThreadLocal时都把它作为静态变量,因此GC无法回收