锁定的代码块在多线程访问的时候以串行的方式执行,可以保证可见性、有序性、原子性
| 概念 | 含义 |
|---|---|
| 可见性 | 每个线程有自己的内存缓存,其他线程缓存在本线程中不可见 |
| 原子性 | 对于非单一的指令,要么都执行,要么都不执行 |
| 有序性 | 编译优化对指令进行重排序,只保证重排序的结果和没排序的结果一致(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)一直被同一个线程访问,那么该线程可以自动获取偏向它锁而无需申请; |
| 轻量级锁 | 若一个偏向锁有了另一线程在申请,此时该锁膨胀为轻量级锁,非偏向线程通过自旋获取这个锁 |
| 重量级锁 | 若自旋一段时间还没取得锁,则线程休眠,锁膨胀为重量级锁;重量级锁在申请时会阻塞线程; |
版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。



