概览
Lock接口
锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁可以防止多个线程同时访问共享资源,有些锁例外,如读写锁。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而且JavaSE5之后,并发包中新增了Lock接口用来实现锁功能。它提供了与synchronized关键字类似的同步功能,只是在使用的时候需要显式的获取和释放锁。
LOCK接口的实现基本都是通过聚合一个同步器的子类来完成线程的访问控制的
队列同步器AQS
队列同步器,是用来构建锁或者其他同步组件的基础框架,他使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源的获取线程的排队工作,并发包的作者期望他能成为大部分同步需求的基础。
同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。可以这样理解二者的关系:锁是面向使用者的,它定义了使用者与使用者与锁的交互的接口,隐藏了实现细节;同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步器状态管理、线程的排队、等待和唤醒等底层操作。
它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)
AQS定义了两种资源共享方式
- 独占式资源-ReentrantLock
- 共享资源-Smaphore/CountDownKatch
队列同步器的接口
三个方法:
- getState():获取当前的同步状态
- setState():设置当前的同步状态
- compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能保证状态设置的原子性。
同步器提供的模板方法基本上分为三类:独占式获取与释放同步状态、共享式获取与释放同步状态和查询同步队列中的等待线程情况。
队列同步器的实现分析
同步器的底层实现:同步队列、独占式同步状态获取与释放、共享式同步状态获取与释放以及超时获取同步状态等同步器的核心数据结构与模板方法。
- 同步队列
同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构成一个节点,并将其加入同步队列,同时阻塞当前线程,当同步状态释放时,会把节点中的线程唤醒,再使其尝试获取同步状态
同步队列遵循FIFO,首节点是获取同步状态成功的节点,首节点的线程在释放同步状态时,会唤醒后继节点,而后继节点将会在获取同步状态成功时将自己设置为首节点。 - 独占式同步状态获取与释放
通过调用同步器的acquire方法可以获取同步状态,该方法对中断不敏感,也就是线程获取同步状态失败后进入同步队列,后续对线程进行中断操作时,线程不会从同步队列中移除。 - 共享式同步状态的获取与释放
共享式获取和独占式获取最主要的区别在于同一时刻能否有多个线程同时获取到同步状态。以文件的读写为例,如果一个程序在对文件进行读操作,那么这一时刻对于该文件的写操作均被阻塞,而读操作能够同时运行 - 独占式超时获取同步状态
通过调用同步器的doAcquireNanos方法可以超时获取过程,即在指定时间内获取同步状态,如果获取到的同步状态则返回true,否则则返回false - 自定义同步组件—TwinsLcok
重入锁 ReentrantLock
重入锁ReentrantLock,支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁还支持获取锁时的公平和非公平性的选择。
重入:当一个线程调用Mutex的lock()方法获取锁之后,如果再次调用lock方法之后,该线程会将自己阻塞,原因是Mutex在实现的时候没有考虑占有锁的线程再次获取锁的情况,在调用获取锁方法的时候返回了false,导致该线程被阻塞,而Synchronized关键字隐式支持重进入,比如说一个synchronized修饰的递归方法,在方法执行的时候,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
公平:在时间维度上,先对锁进行获取的请求一定先被满足,那么这个锁一定是公平的,反之,是不公平的。公平的获得锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。ReentrantLock提供了一个构造函数,能够控制锁是否公平。
读写锁
Mutex和ReentrantLock基本都是排它锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个线程访问,但是在写线程访问的时候,所有的读线程和其他写线程均被阻塞,读写锁维护了一对锁,一个读锁一个写锁,通过分离读锁和写锁,使得并发机制与其他锁有很大区别。
参考数据库:
对数据库的操作两个有代表性的为读和写,也就是查和改(删、增也归为改),为了应对大并发对数据交互的影响,数据库有四种隔离等级,未提交读、已提交读、可重复读、串行化。在Java的并发中,情况考虑的非常简单,不考虑提交与否,也只考虑单个数据。
ReentrantReadWriteLock的实现
读写状态的设计、写锁的获取和释放、读锁的获取与释放以及锁降级。
- 读写状态的设计
- 写锁状态的获取与释放
- 读锁状态的获取与释放
- 锁降级
Condition接口
任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object),主要包括wait(),wait(long timeout),notify()以及notifyAll()方法。这些方法与Synchronized方法搭配,可以实现等待/通知模式,Condition接口也提供类似的Object的监视器方法,与Lock配合实现等待/通知模式。
但两者在使用方式以及功能特性上还是有差别。
对比项 | Object Monitor Methods | Condition |
---|---|---|
前置条件 | 获取对象锁 | 调用Lock.lock()获取锁或调用Lock.newCondition()获取condition对象 |
调用方式 | 直接调用:Object.wait() | 直接调用condition.await() |
等待队列个数 | 一个 | 多个 |
当前线程释放锁并进入等待状态 | 支持 | 支持 |
当前线程释放锁并进入等待状态,在等待中不响应中断 | 不支持 | 支持 |
当前线程释放锁,并进入超时等待状态 | 支持 | 支持 |
当前线程释放锁并进入等待状态到将来的某一个时间 | 不支持 | 支持 |
唤醒队列中的一个线程 | 支持 | 支持 |
唤醒等待队列中的全部线程 | 支持 | 支持 |