相关文章:
【多线程】一、线程状态切换
【多线程】二、线程优先级&守护线程
【多线程】三、线程访问变量
【多线程】四、线程异常抓捕
【多线程】五、线程池
【多线程】六、锁与同步
【多线程】七、阻塞队列
【多线程】八、异步计算结果获取
【多线程】九、Android异步任务
锁定的代码块在多线程访问的时候以串行的方式执行,可以保证可见性、有序性、原子性
概念 | 含义 |
---|---|
可见性 | 每个线程有自己的内存缓存,其他线程缓存在本线程中不可见 |
原子性 | 对于非单一的指令,要么都执行,要么都不执行 |
有序性 | 编译优化对指令进行重排序,只保证重排序的结果和没排序的结果一致(happens-before规则) |
Lock和Condition
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 锁测试,代替lock.lock()发生死锁时可超时终止
// lockInterruptibly()无休止尝试直到被interrupt();
if (lock.tryLock(3000, TimeUnit.MILLISECONDS)) {
try {
if (不满足业务条件) {
condition.await();
// 等待被唤醒: condition.signalAll();
}
// 获取锁成功, do ...
} finally {
lock.unlock();
}
} else { //
// 获取锁超时, do ...
}
同步修饰
synchornized
方法默认用当前对象
或class对象
作为锁;
synchornized
代码块需要显示加对象锁;
对象锁只有一个默认条件(Object#wait()
,Object#notifyAll()
);
锁相关
-
有锁编程:从悲观角度看待,认为并发操作一定导致数据修改,必须加锁保证数据的正确;
-
无锁编程:从乐观角度看待,认为并发操作不一定导致数据修改,通常使用CAS算法实现;如
java.concurrent.atomic.*
, 但CAS可能会导致ABA问题,可用AtomicMarkableReference<T>
或AtomicStampedReference<T>
避免 -
分段设计: 一种锁的设计思想;如
ConcurrentHashMap
中以每个Hash桶链表作为锁,put操作时对某个段加锁,这样实现了并行插入; -
自旋设计: 线程在获取锁的时候不立即阻塞而是通过忙循环尝试,减少线程上下文切换的消耗。这一行为会消耗CPU,基于JVM的线程调度可让出忙循环时间片不会形成死锁;
公平性 | 说明 |
---|---|
非公平 | 线程获取锁的顺序不一定是申请顺序,而为保证最大吞吐量由系统自由调度; 可能会导致优先级反转或饥饿现象 |
公平 | 线程获取锁的顺序和申请的顺序一致;ReentrantLock 底层通过AbstractQueueSynchronizer (AQS)实现调度,可指定为公平策略;synchronized 修饰无法构造公平锁; |
可重入性 | 说明 |
---|---|
可重入(递归锁) | 外层方法获取锁以后进入内层方法可自动取得锁;ReentrantLock 和synchronized 都是可重入锁; |
不可重入 | 同一线程,内层方法不能取得外层方法的锁,阻塞形成死锁 |
共享性 | 说明 |
---|---|
独享(互斥锁) | 锁在同一时刻只能被一个线程获取;ReentrantLock 和synchronized 都是独享锁; |
共享(读写锁) | 锁在同一时刻可以被多个线程获取;ReadWriteLock 读锁通过AQS实现读取与读取共享; |
锁膨胀 | 说明 |
---|---|
偏向锁 | 如果一个锁(synchronized )一直被同一个线程访问,那么该线程可以自动获取偏向它锁而无需申请; |
轻量级锁 | 若一个偏向锁有了另一线程在申请,此时该锁膨胀为轻量级锁,非偏向线程通过自旋获取这个锁 |
重量级锁 | 若自旋一段时间还没取得锁,则线程休眠,锁膨胀为重量级锁;重量级锁在申请时会阻塞线程; |