并发编程 - 重入锁以及AQS源码

一、J.U.C简介

Java.util.concurrent 并发工具包,包括线程池、阻塞队列、计时器、同步器、并发集合等;

二、Lock的基本应用

J.U.C的核心组件。
最先出现的是Synchronized,但是呢,对锁的使用不够灵活,无法去控制锁的释放等,所以就引出了Lock;

重入锁,锁允许重入,唯一 一个实现了lock接口的类,某个线程在得到锁之后,再次访问这个带有同步锁的方法的时候,就不再需要抢占锁或者获得锁了,只需要记录他获得锁的次数即可。如:ReetrantLock 。

三、ReentrantLock重入锁

t1 如果调用lock.lock()方法获得锁以后,如果t1再一次调用这个方法的时候,就不会再阻塞,而是直接增加重入次数。
Synchronized和ReentrantLock都是支持重入的。
重入锁的设计目的是解决死锁问题的。

3.1 锁的基础使用

public class ReetrantLockDemo {

    static Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        lock.lock(); //获得一个锁
        lock.unlock(); //释放锁
    }
}

3.2 读写锁

读写锁 = 读锁 + 写锁;

public class RWLock {
    static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); //重入读写锁

    static Map<String,Object> cacheMap = new HashMap<>();

    static Lock read = reentrantReadWriteLock.readLock();

    static Lock write = reentrantReadWriteLock.writeLock();

    public static final Object get(String key) {
        System.out.println("begin read data: " + key);
        read.lock(); //获得读锁
        try {
            return cacheMap.get(key);
        } finally {
            read.unlock();
        }
    }

    public static final Object put(String key,Object value) {
        System.out.println("begin write data: " + key);
        write.lock(); //获得写锁
        try {
            return cacheMap.put(key, value);
        } finally {
            write.unlock();
        }
    }
}

分析:如果线程a调用put方法获得写锁,那么线程b调用get方法获得读锁的时候就会阻塞,直到线程A释放了锁之后,线程B才有机会获得读锁。
但是如果没有线程获得写锁,而线程abcd都调的是get方法读取数据的时候,是不会存在阻塞的。

总结:所有线程都是读的情况下,不会阻塞的;
但是如果有一个线程是写,其他线程在读,会阻塞,保证其他线程读到的都是最新的;
读和读 是可以共享;
读和写 互斥;
写和写 互斥;
读写锁适用于读多写少的场景;

四、AQS原理分析

当多个线程竞争锁的时候,其他线程怎么办?

AbstractQueuedSynchronizer : 同步工具,
相关类图如下:
在这里插入图片描述
AQS的两个功能:
独占(互斥) :一个锁只能被一个线程获取到;
共享(读写锁):一个锁可以被多个线程获取到;

AQS基本实现

底层是一个双向链表,线程存在竞争的时候,没办法获得锁的时候,会被封装成一个Node,加入到链表里面。

五、AQS源码分析

总体流程图分析如下:
在这里插入图片描述

5.1 lock.lock() 源码// 加锁

CAS原子性操作,至少有两个参数,预期值和修改值,
如果预期值符合预期,那么就进行修改;反之,则不进行修改。这里锁的底层用的是CAS原理。
第一步:如果锁的状态是无线程占用,那么更新锁的状态并设置拥有锁的线程;
在这里插入图片描述
第二步:如果有其他线程已经拥有了,则进行如下操作:
在这里插入图片描述
(1)、tryAcquire(),尝试一下是否可以获得锁
在这里插入图片描述
(2)、addwaiter和acquireQueue
addwaiter(),封装当前线程为Node节点,并插入到链表中
在这里插入图片描述
acquireQueue():
在这里插入图片描述

5.2 lock.unlock() 源码 // 解锁

在这里插入图片描述

六、公平锁和非公平锁

NonfairSync 非公平锁 允许插队
FairSync 公平锁 不允许插队
实现比较:
区别一:lock方法,非公平锁一上来插队,即设置锁的状态
在这里插入图片描述
区别二:tryAcquire方法
在这里插入图片描述

七、ReentrantLock和Synchroinzed的区别

1、Synchroinzed是一个关键字,而ReentrantLock是J.U.C的组件;
2、释放锁的方式不同
Synchroinzed的释放方式:异常 + 同步块执行完毕;
ReentrantLock:调用unlock()方式;
3、灵活性,ReentrantLock比Synchroinzed使用起来更灵活些。
4、死锁,lock可以通过tryLock()预防死锁。
5、公平性和非公平性。

原创文章 88 获赞 21 访问量 3万+

猜你喜欢

转载自blog.csdn.net/cfy1024/article/details/100067302
今日推荐