目录
四、AbstractQueuedSynchronizer(AQS)
一、 可重入锁与非可重入锁
可重入锁,就是当一个占有锁的线程再次进入到这个锁的代码块时,可以直接获得锁并且执行,而无需等待。
与之相对的,就是非可重入锁,当一个占有锁的线程再次进入到这个锁块时仍然需要等待获取锁。
Java中的synchronized和ReentrantLock都是可重入锁。
可重入锁的优点是:同一个线程获取同一个锁时不会造成死锁,且加快了执行的速度。
我们举一个例子来看下可重入锁:
public class Main {
public static void funcA(){
synchronized (Main.class){ //加锁
System.out.println("a");
funcB();
System.out.println("done");
}
}
public static void funcB(){
synchronized (Main.class){ //加锁,同一把锁
System.out.println("b");
}
}
public static void main(String[] args) {
funcA();
}
}
上面的代码,funcA与funcB中加的是同一把锁,funcA先获得了锁开始执行。之后funcA中调用了funcB,funcB同样也加了锁,如果锁不可重入,就会造成死锁,致使后面的信息打印不出来。而synchronized是可重入锁,避免了死锁。
执行结果:
二、 公平锁与非公平锁
公平锁:获取锁的机会按照严格的请求时间先后顺序,有个先来后到。
非公平锁:不按照先后顺序,谁来了都可以tryAcquire试一下,谁能成功全靠运气。
公平锁与非公平锁的实现原理:
将需要获取锁的线程加入到一个FIFO的队列中,按照先进先出的原则,每次都是排在第一个的线程拥有获得锁的机会。
公平锁与非公平锁的比较: 哪一个更好一点呢?
公平锁比非公平锁多用了一个队列,耗费了一点点内存,但是它可以使所有需要获取锁的线程在排队的时候处于等待状态(除了排在队头的可以处于running),CPU轮询的时候就无需对这些处于等待状态的线程进行调度,节省了CPU资源。因此,公平锁更好一些。
三、独占锁与共享锁
独占锁,就是一个线程获取锁之后,在其释放锁前,其他线程就不能获取这个锁;
共享锁,就是一个线程获取锁之后,其他的线程也可以获取锁并执行同步代码块。
ReentrantReadWriteLock ,它的写锁就是一个独占锁,它的读锁就是一个共享锁。
- 当上了写锁之后,就只允许这一个线程写,其他线程就不能读不能写了;
- 而当只有读锁的时候,多个线程可以共同读取数据,因为只读的时候不会产生线程安全性问题。
四、AbstractQueuedSynchronizer(AQS)
AbstractQueuedSynchronizer内部维护了一个FIFO的队列和一个 int 状态变量,队列用来实现排队,状态变量用来控制是否可以获得锁。 它为实现依赖于FIFO队列的阻塞锁和相关同步器提供了一个框架。
AQS实现阻塞锁通常是通过子类extends AQS来实现的。
AQS定义了很多定义为final的方法,用来提供服务。
当子类继承AQS之后,还有几个方法需要自己实现,用来完成锁的获取和释放。
tryAcquire(int arg) | 尝试获取锁(独占) |
tryRelease(int arg) | 尝试释放锁(独占) |
tryAcquireShared(int arg) | 尝试获取锁(共享) |
tryReleaseShared(int arg) | 尝试释放锁(共享) |
isHeldExclusively | 返回boolean, 是否被独占 |
如果是实现独占锁,就实现前两个方法;
如果是实现共享锁,就实现后两个方法。
这些个方法的原始写法只是抛出一个异常,需要继承者自己实现。
AQS中的其他方法及其作用下面以ReentrantLock的实现为例来讲解。
五、ReentrantLock
ReentrantLock是通过继承AQS来实现的,它的类结构图是这样
ReentrantLock中包含了一个Sync类,Sync类继承了AQS的诸多方法。
NonFairSync 和 FairSync继承了Sync类,它们两个是真正用来干活的,它们各有两个方法 lock 和 tryAcquire用来实现获取锁,FairSync实现公平锁,NonFairSync实现非公平锁。
看一下ReentrantLock的源码实现
1. lock方法
非公平锁先尝试去独占,如果不成功再和公平锁一样调用AQS的acquire()方法。在acquire()方法中,调用了addWaiter,正是addWaiter方法中将想要获取锁的线程加入了FIFO的Queue中。
2. tryAcquire方法
非公平锁直接调用Sync类的nonfairTryAcquire方法,其实nonfairTryAcquire的实现与公平锁的tryAcquire差不多,只是多加了一个判断线程队列是否为空的步骤而已。
如果线程队列不为空又不是同一个线程重入,则直接return false,会使处于Queue的头节点的线程优先获取锁,从而实现获取时间上的公平。
3. 可重入的实现
在tryAcquire方法中,else if 判断如果当前线程是已经独占了锁的线程,那么只是将锁的层数+1,然后返回true,就相当于又获取了锁,从而实现了可重入。
六、用AQS实现一个自己写的重入锁
import javax.management.RuntimeErrorException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class MyLock implements Lock{ //implements Lock,声明MyLock为锁
private static Helper helper = new Helper();
private static class Helper extends AbstractQueuedSynchronizer{ //同Sync一样extends AQS实现功能
@Override
protected boolean tryAcquire(int arg) {
int state = getState();
if(0 == state){
if(compareAndSetState(arg)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}
//实现可重入
else if(getExclusiveOwnerThread() == Thread.currentThread()){
setState(state + arg);
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
//容错处理
if (getExclusiveOwnerThread() != Thread.currentThread()){
throw new RuntimeException();
}
int state = getState() - arg;
if (state == 0){
setExclusiveOwnerThread(null);
setState(state); //这里还有下面都有setState(state)但不能合并提前到前面,否则先setState(state)就会造成释放另一个线程先运行,造成原子性失败
return true;
}
setState(state); //setState(state)不能合并提前到前面
return false;
}
@Override
protected boolean isHeldExclusively() {
return 0 != getState();
}
protected Condition getCondition(){
return new ConditionObject();
}
}
@Override
public void lock() {
helper.acquire(1);
}
@Override
public void unlock() {
helper.release(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
helper.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return helper.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return helper.tryAcquireNanos(1, unit.toNanos(time));
}
@Override
public Condition newCondition() {
return helper.getCondition();
}
}
测试代码
public class Main {
private static volatile int num = 0;
private static MyLock lock = new MyLock();
public static void main(String[] args) {
//起两个线程交替打印确定不会出现线程安全性问题
new Thread(new Runnable() {
@Override
public void run() {
while(true){
if(lock.tryLock()){
++num;
System.out.println(Thread.currentThread().getName() + " " + num);
lock.unlock();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
if(lock.tryLock()){
System.out.println(Thread.currentThread().getName() + " " + num);
++num;
lock.unlock();
}
}
}
}).start();
}