目录
一、常见的锁策略
乐观锁 vs 悲观锁
重量级锁 vs 轻量级锁
读写锁&普通互斥锁
自旋锁&挂起等待锁
可重入锁&不可重入锁
公平锁&非公平锁
synchronized实现了哪些锁策略?
二、Compare And Swap 比较并交换
基于CAS的应用
CAS实现自旋锁
CAS 的 ABA 问题
解决ABA问题
三、Synchronized 原理
四、其他的优化操作
锁消除
锁粗化
五、JUC
一、常见的锁策略
乐观锁 vs 悲观锁
重量级锁 vs 轻量级锁
在实现锁的过程中,消耗的资源多不多
轻量级锁:可以纯用户态的锁,消耗的资源比较小
重量级锁:可能会调用到系统的内核态,消耗的资源比较多
读写锁&普通互斥锁
在现实中并不是所有的锁都要互斥,互斥必然会消耗很多的资源,所以优化出读写锁
读锁:共享锁,读与读可以同时拿到锁资源
写锁:排他锁,不能与 写写,写读,读写
普通互斥锁:synchronized,只能一个线程拿到锁资源,其他的要参与锁竞争,没有竞争到锁的时候就要阻塞等待
自旋锁&挂起等待锁
自旋锁:不停的询问资源是否被释放,如果释放了第一时间可以获取锁资源
挂起等待锁:等待通知之后再去竞争锁,并不会第一时问获取到锁资源
可重入锁&不可重入锁
可重入锁:对于同一个锁对象可以加多次锁
不可重入锁:不能对同一个锁对象加多次锁
公平锁&非公平锁
公平锁:先排队等待的线程先获取到锁资源
非公平锁:没有先来后到这么一说,谁抢到是谁的
所有有争抢的事情,绝大多数都是不公平的
synchronized实现了哪些锁策略?
- 既是乐观锁与是悲观锁
- 既是轻量级锁与重量级锁 轻量级锁是基于自旋锁实现的,重量级锁是基于挂起等待锁实现的
- 是普通互斥锁
- 既是自旋锁与是挂起等待锁
- 是可重入锁
- 是非公平锁
自旋锁是基于CAS实现的
二、Compare And Swap 比较并交换
基于CAS的应用
CAS实现自旋锁
自旋锁伪代码:
public class SpinLock {
private Thread owner = null;
public void lock(){
// 通过 CAS 看当前锁是否被某个线程持有.
// 如果这个锁已经被别的线程持有, 那么就自旋等待.
// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.
while(!CAS(this.owner, null, Thread.currentThread())){
}
}
public void unlock (){
this.owner = null;
}
}
CAS 的 ABA 问题
ABA 问题:
假设存在两个线程 t1 和 t2. 有一个共享变量 num, 初始值为 A.接下来 , 线程 t1 想使用 CAS 把 num 值改成 Z, 那么就需要先读取 num 的值 , 记录到 oldNum 变量中 .使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z.但是 , 在 t1 执行这两个操作之间 , t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A线程 t1 的 CAS 是期望 num 不变就修改 . 但是 num 的值已经被 t2 给改了 . 只不过又改成 A 了 . 这个时候 t1 究竟是否要更新 num 的值为 Z 呢 ?
解决ABA问题
给要修改的值, 引入版本号.版本号只增不减, 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.
- CAS 操作在读取旧值的同时, 也要读取版本号. 真正修改的时候, 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.
- 如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了).
这就好比 , 判定这个手机是否是翻新机 , 那么就需要收集每个手机的数据 , 第一次挂在电商网站上的手机记为版本1, 以后每次这个手机出现在电商网站上 , 就把版本号进行递增 . 这样如果买家不在意这是翻新机, 就买 . 如果买家在意 , 就可以直接略过 .
三、Synchronized 原理
假设男主是一个锁 , 女主是一个线程 . 如果只有这一个线程来使用这个锁 , 那么男主女主即使不领证结婚( 避免了高成本操作 ), 也可以一直幸福的生活下去 .但是女配出现了 , 也尝试竞争男主 , 此时不管领证结婚这个操作成本多高 , 女主也势必要把这个动作完成了, 让女配死心 .
四、其他的优化操作
锁消除
编译器+JVM 判断锁是否可消除. 如果可以, 就直接消除.
有些应用程序的代码中 , 用到了 synchronized, 但其实没有在多线程环境下 . ( 例如 StringBuffer) 此时每个 append 的调用都会涉及加锁和解锁 . 但如果只是在单线程中执行这个代码 , 那么这些加锁解锁操作是没有必要的, 白白浪费了一些资源开销 .
锁粗化
StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");
五、JUC
java.util.concurrent 包的简称,JDK1.5之后对多线程的一种实现,这个包下放的类都和多线程有关,提供了很多工具类
Callable 接口
public class Demo03_Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 先定义一个线程的任务
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += i;
TimeUnit.SECONDS.sleep(1);
System.out.println("等待1秒");
}
// 返回结果
return sum;
}
};
// 通过FutureTask类来创建一个对象,这个对象持有callable
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 创建线程并指定任务
Thread thread = new Thread(futureTask);
// 让线程执行定义好的任务
thread.start();
// 获取线程执行的结果
System.out.println("等待结果...");
Integer result = futureTask.get();
// 打印结果
System.out.println(result);
}
}
ReentrantLock
可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全.
- synchronized 是一个关键字, 是 JVM 内部实现的(大概率是基于 C++ 实现). ReentrantLock 是标准
- synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活, 但是也容易遗漏 unlock.
- synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃.
- synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启公平锁模式.
public class Demo05_ReentrantLock {
/**
* ReentrantLock可以根据不同的Condition去休眠或唤醒线程
* 同一把锁可以分为不同的休眠或唤醒条件
*/
private static ReentrantLock reentrantLock = new ReentrantLock();
// 定义不同的条件
private static Condition boyCondition = reentrantLock.newCondition();
private static Condition girlCondition = reentrantLock.newCondition();
public static void demo05_Condition () throws InterruptedException {
Thread threadBoy = new Thread(() -> {
// 让处理男生任务的线程去休眠
try {
boyCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒处理女生任务的线程
girlCondition.signalAll();
});
Thread threadGirl = new Thread(() -> {
// 让处理女生任务的线程去休眠
try {
girlCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒处理男生任务的线程
boyCondition.signalAll();
});
}
/**
* 创建读写锁
*/
public static void demo04_ReadWriteLock () {
// 创建
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 获取读锁, 共享锁,读与读可以同时进行
readWriteLock.readLock();
// 获取写锁,排他锁(互斥锁),读写,写读,写写不能共存
readWriteLock.writeLock();
}
/**
* 演示创建一个公平锁
*/
public static void demo03_fair () {
// 通过构造方法,传入true时为公平锁,false为非公平锁,默认为false
ReentrantLock reentrantLock = new ReentrantLock(true);
}
/**
* 模拟业务中如果出现异常情况,如何释放锁
*/
public static void demo02 () throws Exception {
// 创建一个ReentrantLock对象
ReentrantLock reentrantLock = new ReentrantLock();
// 加锁
reentrantLock.lock();
try {
// TODO : 业务逻辑
throw new Exception("业务出现异常");
} finally {
// 保证出现异常的时候也可以释放锁
reentrantLock.unlock();
}
}
/**
* 演示基本方法
* @throws InterruptedException
*/
public static void demo01_lock() throws InterruptedException {
// 创建一个ReentrantLock对象
ReentrantLock reentrantLock = new ReentrantLock();
// 加锁
reentrantLock.lock();
// 尝试加锁, 死等
reentrantLock.tryLock();
// 尝试加锁,有超时时间
reentrantLock.tryLock(1, TimeUnit.SECONDS);
// 释放锁
reentrantLock.unlock();
}
}