Java并发-04-Java并发中的Lock

并发编程领域,有两大核心问题:

一个是互斥,即同一时刻只允许一个线程访问共享资源;

一个是同步,即线程之间如何通信、协作。

Java SDK并发包通过Lock和Condition两个接口来实现管程,其中Lock用于解决互斥问题,Condition用于解决同步问题

Java 语言内置的管程里只有一个条件变量,而Lock&Condition实现的管程是支持多个条件变量的,这是二者的一个重要区别。

1-Lock

1.1-Lock出现的原因

除了支持类似synchronized隐式加锁的lock()方法外,还支持超时、非阻塞、可中断的方式获取锁,这三种方式为我们编写更加安全、健壮的并发程序提供了很大的便利。

lock具备了synchronized锁不具备的功能;

// 支持中断的API
void lockInterruptibly() throws InterruptedException;
// 支持超时的API
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 支持非阻塞获取锁的API
boolean tryLock();

1.2-Lock经典应用场景

Lock和Condition经典应用-阻塞队列

我们熟悉的ArrayBlockingQueue里面就定义了ReentrantLock和两个Condition。

构造方法中

offer-入队-带有超时属性的

poll-出队-带有超时属性

2-AQS同步器

AQS:AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架,许多同步类实现都依赖于该同步器。

AQS 用状态属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁

  • 独占模式是只有一个线程能够访问资源,如 ReentrantLock

  • 共享模式允许多个线程访问资源,如 Semaphore,ReentrantReadWriteLock 是组合式

2.1-核心思想

  • 如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置锁定状态

  • 请求的共享资源被占用,AQS 用队列实现线程阻塞等待以及被唤醒时锁分配的机制,将暂时获取不到锁的线程加入到队列中

CLH 是一种基于单向链表的高性能、公平的自旋锁,AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。

2.2-设计原理

2.2.1-state的设计

  • state变量(volatile) 使用了 32bit int 来维护同步状态,独占模式 0 表示未加锁状态,大于 0 表示已经加锁状态。

  • state 使用 volatile 修饰配合 cas 保证其修改时的原子性

  • state 表示线程重入的次数(独占模式)或者剩余许可数(共享模式)

2.2.2-Node 节点中 waitstate 设计

  • 使用 volatile 修饰配合 CAS 保证其修改时的原子性

  • 表示 Node 节点的状态,有以下几种状态:

// 默认为 0
volatile int waitStatus;
// 由于超时或中断,此节点被取消,不会再改变状态
static final int CANCELLED =  1;
// 此节点后面的节点已(或即将)被阻止(通过park),【当前节点在释放或取消时必须唤醒后面的节点】
static final int SIGNAL    = -1;
// 此节点当前在条件队列中
static final int CONDITION = -2;
// 将releaseShared传播到其他节点
static final int PROPAGATE = -3;

2.3.2-队列设计

使用了 FIFO 先入先出队列,并不支持优先级队列,同步队列是双向链表,便于出队入队

// 头结点,指向哑元节点
private transient volatile Node head;
// 阻塞队列的尾节点,阻塞队列不包含头结点,从 head.next → tail 认为是阻塞队列
private transient volatile Node tail;

static final class Node {
    // 枚举:共享模式
    static final Node SHARED = new Node();
    // 枚举:独占模式
    static final Node EXCLUSIVE = null;
    // node 需要构建成 FIFO 队列,prev 指向前继节点
    volatile Node prev;
    // next 指向后继节点
    volatile Node next;
    // 当前 node 封装的线程
    volatile Thread thread;
    // 条件队列是单向链表,只有后继指针,条件队列使用该属性
    Node nextWaiter;
}

2.3模板方法

一般使用的方式是内部有一个类继承AbstractQueuedSynchronizer,然后重写如下方法

isHeldExclusively()        //该线程是否正在独占资源。只有用到condition才需要去实现它
tryAcquire(int)            //独占方式。尝试获取资源,成功则返回true,失败则返回false
tryRelease(int)            //独占方式。尝试释放资源,成功则返回true,失败则返回false
tryAcquireShared(int)    //共享方式。尝试获取资源。负数表示失败;0表示成功但没有剩余可用资源;正数表示成功且有剩余资源
tryReleaseShared(int)    //共享方式。尝试释放资源,成功则返回true,失败则返回false

2.4-jdk源码使用AQS

jdk中各种新增的锁类都是在AQS的基础上进行的,比如Semaphore,ReadWriteLock,ReentrantLock。

2.4.1-共享锁Semaphore

2.4.2-排他锁ReentrantLock

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值