| synchronized | Lock | |
|---|---|---|
| 依赖 | JVM 的 monitor | JUC 包下的 API |
| 释放锁 | 不需要,通过正常异常两种场景的 monitorexit 保证自动释放 | 需要 |
| 等待可中断 | 不可以,除非完成或抛出异常 | 可以,通过超时方法或 lockInterruptibly() |
| 公平 | 不公平 | 都可以,默认非公平,但可以设置公平 |
| 能否精确唤醒 | 不能,要不随机一个要不全部 | 可以通过 Condition 分组精确唤醒 |
| 未能获取锁时 | 休眠,直到 CPU 再次轮到此线程 | 可以通过 trylock() 立即返回,线程不休眠 |
public static synchronized void method(){ }
public synchronized void method1(){ }
public void method2(){ synchronized (new Object()){ } }
synchronized 的本质
monitorenter 和 monitorexit 进入、释放monitorenter 配两个 monitorexitmonitorexitACC_SYNCHRONIZED 访问标记与非同步方法做出区分ObjectMonitor
ObjectMonitor 属性
源码
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
//多线程竞争锁进入时的单向链表
_cxq = NULL ;
FreeNext = NULL ;
//_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
ObjectMonitor 对象的地址了synchronized 的原理
monitorenter 时,判断 _count 的值_count = 0 ,表示锁没被占用,可以加锁_count != 0 ,表示加锁_owner 是否是当前线程,如果是则可以重入,同时 _recursions + 1,否则不行monitorexit 时,_recursions - 1,若此时_recursions = 0 则可以退出锁基础场景
有一个资源 a,a 有一个值属性
两个线程,两个线程执行相同的轮次
一个线程当资源值 ==0 时 + 1
一个线程当资源值 !=0 时 - 1
实现示例
public class ResourceData {
private volatile int value;
private Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();//等待、通知的条件
public int increment() {
lock.lock();
try {
// 防虚假唤醒
while(value != 0){
condition.await(); //1
}
value++;
System.out.println("++++++++++++++++");
condition.signalAll();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
return this.value;
}
public int decrement() {
lock.lock();
try {
while(value == 0){
condition.await();
}
value--;
System.out.println("----------------");
condition.signalAll();
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
return this.value;
}
public static void main(String[] args) {
ResourceData data = new ResourceData();
new Thread(()->{
for(int i = 0; i < 5; i++) {
data.increment();
}
}).start();
new Thread(()->{
for(int i = 0; i < 5; i++) {
data.decrement();
}
}).start();
}
}
虚假唤醒
当线程发起对其他线程发起唤醒后
除了执行任务锁必须的线程外,还可能唤醒了其他冗余或无关的线程,这些线程可能超量或错误的执行任务
还因为,在操作系统设计之初,就存在不是由通知触发唤醒的可能性(比如由中断唤醒)
为避免虚假唤醒,线程在执行任务之前,应该判断当前场景是否具备自身执行任务的前提条件
同时,线程的唤醒位置是线程的等待位置,即上例中 //1 的位置,为了使唤醒的线程再次判断执行条件,因此需要使用 while
精准唤醒场景
多线程的按需调用,
A 线程执行 1 次,随后 B 线程执行 2 次,最后 C 线程执行 3 次
重复 5 轮
在 lock 中,可以通过对应的 Condition 精准的唤醒指定的线程
在 synchronized 中,只能通过全部唤醒,然后根据标志位(下面示例中的 flag)使不需要唤醒的线程再 wait()
public class OrderResourceData {
private int flag = 0;
private ReentrantLock lock = new ReentrantLock();
// 几个线程几个条件
private Condition[] conditions = new Condition[]{lock.newCondition(), lock.newCondition(), lock.newCondition()};
private void work(int n){
for (int i = 0; i < n; i++) {
System.out.println(Thread.currentThread().getName() + " : " + (i+1));
}
}
public void preciseWork(int flag, int n){
for (int i = 0; i < 5; i++) {
lock.lock();
Condition condition = conditions[flag];
try {
while(this.flag!=flag) {
condition.await();
}
work(n);
System.out.println();
// 切换下一个线程
// 一个 n 个条件,flag 在 [0 , n-1] 中循环
this.flag = (this.flag + 1) % conditions.length;
// 精准唤醒
conditions[this.flag].signal();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
OrderResourceData data = new OrderResourceData();
new Thread(()->{
data.preciseWork(0, 1);
},"A").start();
new Thread(()->{
data.preciseWork(1, 2);
},"B").start();
new Thread(()->{
data.preciseWork(2, 3);
},"C").start();
}
}
ReentrantReadWriteLock什么是读写锁
为什么会有读写锁
对于数据,通常读的场景远比写的场景多,但写的场景通常无法避免
因此,需要存在锁,但需要尽量降低锁对数据在性能方面的影响,这些影响包括
读写锁同时满足上述要求
适用场景
读多写少的场景
public class ReadWriteLockDemo {
private static Map<String,String> map = new HashMap<>();
private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void put(String key,String value){
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+" put "+key);
TimeUnit.MILLISECONDS.sleep(100);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+" put done "+key);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.writeLock().unlock();
}
}
public static void get(String key){
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+" get "+key);
map.get(key);
TimeUnit.MILLISECONDS.sleep(1000);
System.out.println(Thread.currentThread().getName()+" get done "+key);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}
public static void main(String[] args) {
for(int i=0;i<3;i++){
int finalI = i;
new Thread(()->{
put(String.valueOf(finalI),UUID.randomUUID().toString().substring(0,8));
},String.valueOf(i)).start();
}
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<3;i++){
int finalI = i;
new Thread(()->{
get(String.valueOf(finalI));
},String.valueOf(i)).start();
}
}
}
读时无所谓,写时必须每个线程独占整个锁

缺点
锁降级
双重检索示例
//是否被修改的标记
//防止获取锁的间隙,被其他线程抽空完成修改,所有必须是 volatile
volatile boolean writen = false;
void xx(String key,String value){
lock.readLock().lock();
if(!writen){
// writen 一定是 false
// 即近期未被修改,故下面操作基本安全,释放读锁以便获取写锁
lock.readLock().unlock();
// 获取写锁
lock.writeLock().lock();
try {
// 再查一次,因为释放读后获取写之间,有一定的概率被修改
if(!writen ){
write(data);
cacheValid = true;
}
lock.readLock().lock();
} finally {
lock.writeLock().unlock();
}
try{
read(data);
}finally {
lock.readLock().unlock();
}
}
}
StampeLock特点
访问模式
ReentrantReadWriteLock 读锁ReentrantReadWriteLock 读锁缺点
Conditioninterrupt()使用场景
使用方式
StampeLock 可以按 ReentrantReadWriteLock 行为使用
可以按 ReentrantReadWriteLock 行为使用,但细节处有不同
详细见上文 缺点
public class StampedLockDemo {
StampedLock lock = new StampedLock();
int resource = 0;
public void write(){
long stamp = lock.writeLock();
System.out.println("====== write ====== : " + stamp);
try {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) { e.printStackTrace(); }
resource ++;
} finally {
lock.unlockWrite(stamp);
System.out.println("====== writed ======");
}
}
public int read(){
long stamp = lock.readLock();
System.out.println("====== read ====== : " + stamp);
try {
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) { e.printStackTrace(); }
return resource;
} finally {
lock.unlockRead(stamp);
System.out.println("====== read ======");
}
}
public static void main(String[] args) {
StampedLockDemo demo = new StampedLockDemo();
new Thread(()->{demo.read();},"A").start();
try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{demo.write();},"B").start();
}
}

StampeLock 可以按乐观读方式使用
public class StampedLockDemo {
StampedLock lock = new StampedLock();
int resource = 0;
public void write(){
long stamp = lock.writeLock();
System.out.println("====== write ====== : " + stamp);
try {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) { e.printStackTrace(); }
resource ++;
} finally {
lock.unlockWrite(stamp);
System.out.println("====== writed ======");
}
}
public int read(){
long stamp = lock.readLock();
System.out.println("====== read ====== : " + stamp);
try {
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) { e.printStackTrace(); }
return resource;
} finally {
lock.unlockRead(stamp);
System.out.println("====== read ======");
}
}
public int optimisticRead(){
long stamp = lock.tryOptimisticRead();
System.out.println("====== optimistic read ====== : " + stamp);
try {
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) { e.printStackTrace(); }
//falldown to read
if(!lock.validate(stamp))
return read();
return resource;
} finally {
System.out.println("====== optimistic read ======");
}
}
public static void main(String[] args) {
StampedLockDemo demo = new StampedLockDemo();
new Thread(()->{demo.optimisticRead();},"A").start();
try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{demo.write();},"B").start();
}
}