目录
[6、wait 和 notify 为什么要放在 synchronized](#6、wait 和 notify 为什么要放在 synchronized)
1、线程池是如何知道线程任务是否完成
线程池内部:
当把任务丢给线程池去执行,调度工作线程, 通过同步调用执行 run () 方法,正常结束, 等待方法返回 直到结束,
线程外部:
线程池提供了isInterrupted () 方法 判断线程池的运行状态 结果是 意味着任务执行完成 submit 方法 通过 future.get() 方法 正常返回 意味着结束 没有返回则一直执行
引入CountDownLatch 计数器 进行倒计时 有两个方法 :
1、await()阻塞线程
2、countDown() 进行倒计时,一旦倒计时归零,所有阻塞在await()方法的线程都会被释放
2、阻塞队列的有界和无界
阻塞队列是特殊的队列,1、当队列为空,获取队列中元素的消费者线程,它会被阻塞,同时唤醒生产者线程,2、当队列中元素满了,向队列中添加元素的生产者线程,它会被阻塞,同时唤醒消费者线程。
有界队列: 阻塞队列中容纳的个数是有限的,比如去实例化一个ArrayBlocking List,可以在构造方法传一个整形的数字,表示基于数组的阻塞队列中,能够容纳的元素个数,称为
无界队列:就是没有设置固定大小的队列,并不是没有任何限制,而是元素存储量很大。无界队列存在比较大的潜在风险,并发量较大的情况下,线程池中几乎可以无限制的添加任务,容易导致内存溢出。
3、 ConcurrentHashMap底层实现原理
整体架构:
jdk1.8 由数组+红黑树+单向链表构成,默认初始化长度16的数组,由于核心仍是hashmap ,必然会存在hash冲突, 所有ConcurrentHashMap采用链式寻址的方式
解决hash 冲突:当hash冲突比较多的时候,会造成链表长度问题, jdk 1.8 引入红黑树,当数组长度大于64,并且链表长度大于8,单向链表会转化为红黑树。当链表长度小于8,红黑树就会退化为单项链表。
基本功能:
在hashmap基础上提供了并发安全的实现,通过对于node节点去加锁,来保证数据更新的安全性。性能方面优化:
jdk1.8、ConcurrentHashMap 的锁粒度是数组中某一个节点,
jdk1.7 、锁定的是segment 锁的范围要更大,性能上会更低,
引入红黑树,降低数据查询的时间复杂度
当数组长度不够的时候,对数组进行扩容, 引入了多线程并发扩容(多个线程对原始数组进行分片),每个线程负责对一个分片的迁移。
4、死锁发生的原因 怎么避免
死锁:多个锁去竞争同一个资源 造成相互等待,如果没有外部干预、将无法往下执行
导致死锁的条件:
- 互斥条件 共享资源x和y 只能被一个线程占用
- 请求和保持条件 A线程已经取得共享资源x 在等待共享资源y的时候,不释放x
- 其他线程不能强行抢占线程A 占有的资源
- 循环等待:线程A 等待B占有的资源 B等待A占有的资源
解决死锁:
通过人工干预来解决, 重启服务、或者kill 掉线程
破坏其中任意一种 (互斥条件没办法破话) 互斥条件是互斥锁的基本约束
5、CAS机制
CAS是java中unsafe类里面的一个方法,全称:Compare and Swap比较并交换
主要功能: 保证在多线程环境下,对于共享变量修改的一个原子性
例子: 有一个成员变量state默认值为0 ,定义了一个方法,逻辑为,先判断state是否为0,如果为0 就修改为1,在单线程没有任何问题,多线程下会存在原子性问题。 这是是一个 read -write 操作, 一般解决会在方法 加 synchronized同步锁来解决,但是加锁 会带来性能上的损耗, 所以可以采用CAS机制进行优化。
6、wait 和 notify 为什么要放在 synchronized
wait 和notify 为了实现多个线程之间的协调, 阻塞/被唤醒 。他俩必然成对出现,
要实现多线程之间的通信,除了管道流,只能通过共享变量的方法来实现,A线程修改共享变量s ,线程B 获得修改之后共享变量s的值
但是多线程本身具有并行执行的特征,同一时刻多个线程可以同步执行,线程B 在访问共享变量之前,必须要知道线程A已经修改过共享变量S, 修改完同时唤醒等待状态下的线程, synchronized 可以实现互斥条件,实现条件等待条件唤醒,为了避免wait /notify 错误使用,强制放入同步代码块。