目录
一、常见的锁策略
乐观锁 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();
    }
}



















