高并发学习之14锁的源码在梳理

1. 简介

在前面的文章中我们介绍了lock接口AQS同步器,重入锁,condition接口,这篇文章将准备将以上知识点在从源码上梳理一遍。

2. Lock

在java5以后,增加了JUC的并发包且提供了Lock接口用来实现锁的功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。
Lock是一个接口,核心的两个方法lock和unlock,下面是Lock接口源码:

public interface Lock {
	//获取锁
    void lock();
	//获取锁的过程能够响应中断
    void lockInterruptibly() throws InterruptedException;
	//非阻塞式响应中断能立即返回,获取锁放回true反之返回fasle
    boolean tryLock();
	//超时获取锁,在超时内或者未中断的情况下能够获取锁
    boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;
	//释放锁
    void unlock();
	//获取与lock绑定的等待通知组件,当前线程必须获得了锁才能进行等待,进行等待时会先释放锁,当再次获取锁时才能从等待中返回
    Condition newCondition();
}
3. AbstractQueuedSynchronizer(AQS抽象队列同步器)

AQS是用来构建锁或者其他同步组件的基础框架,通过一个int变量来表示当前同步状态,AQS依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失时,AQS会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
Node属性如下:

static final class Node {
  int waitStatus; //表示节点的5种状态
  Node prev; //前继节点
  Node next; //后继节点
  Node nextWaiter; //存储在condition队列中的后继节点
  Thread thread; //当前线程
}

其中节点状态:

  • Cancelled,值为1,由于在同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待,节点进入该状态将不会变化
  • Signal,值为-1,后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行
  • Condition,值为-2,节点在等待队列中,节点线程等待在Condition 上,当其他线程对Condition 调用了 signal()方法后,该节点将会从等待队列中转移到同步队列中,加入到对同步状态的获取中
  • Propagate, 值为-3, 表示下一次共享式同步状态获取将会无条件地被传播下去
  • Initial, 值为0,初始状态
4. 从ReentrantLock重入锁开始

ReentrantLock重入锁是接口Lock 的一种实现,他支持重进入,除此之外,该锁的还支持获取锁时的公平和非公平性选择。下面是重入锁的部分源码:

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;
	
	//构造函数,默认是非公平锁
	public ReentrantLock() {
        sync = new NonfairSync();
    }
    //构造函数,可以选择是否公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
	//非公平锁
	static final class NonfairSync extends ReentrantLock.Sync{
		.....
	}
	//公平锁
  	static final class FairSync extends ReentrantLock.Sync{
		......
	} 
	//继承AQS(同步器)
    abstract static class Sync extends AbstractQueuedSynchronizer {
    	.....
    }
    //线程等待队列
    public Condition newCondition() {
        return this.sync.newCondition();
    }

下面开始一个简单例子:

	private static int count = 0;
    static Lock lock = new ReentrantLock();

    public static void inc() {
        lock.lock();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
        lock.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                RLDemo.inc();
            }).start();
        }
        Thread.sleep(3000);
        System.out.println("result:" + count);
    }

在上面的例子中,生成1000个线程,每个线程都调用inc方法实现count+1。这里补充下,Lock 一般使用static修饰,作为类的一个属性。
在类加载的时候发现static修饰的lock 就会初始化new ReentrantLock()。重入锁的构造函数,就知道他去生成了一个非公平的重入锁实例,而重入锁是通过AQS生成的,AQS是继承AbstractOwnableSynchronizer抽象类。AbstractOwnableSynchronizer只是记录当前被哪个线程持有。ReentrantLock初始化时序图如下:
ReentrantLock初始化时序图
ReentrantLock初始化完成后会有三个属性:

  • Node head ,头节点来源于AQS中Node
  • Node tail 尾点来源于AQS中Node
  • int state 同步状态
  • Thread exclusiveOwnerThread 当前持有同步状态的线程来源于AQS父类中AbstractOwnableSynchronizer

如图:
ReentrantLock初始化完成属性图

lock初始完成后,将会调用其lock方法加锁,注意lock()、unlock方法是对于锁的使用者来说,而lock之后是对于锁的开发者。

lock()加锁时序图如下:
lock加锁时序图
关于lock源码大家可以到AQS同步器,重入锁,看下具体代码说明。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值