AbstractQueuedSynchronized
AbstractQueuedSynchronized(AQS)是用来构建锁和其他同步组件的基础框架。它使用了一个int成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。
同步器的主要使用方式是继承,子类同步器在实现AQS时只需要实现共享资源state的获取与释放方式,至于具体线程等待队列的维护AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
- tryAcquire(int):独占方式。尝试获取资源
- tryRelease(int):独占方式。尝试释放资源
- tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
- tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
AQS定义了两种资源共享方式:Exclusive(独占)和Share(共享),不同的自定义同步器争用共享资源的方式也不同。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
static final class Node {
//标记节点在共享式模式下等待
static final Node SHARED = new Node();
//标记节点在独占式模式下等待
static final Node EXCLUSIVE = null;
//线程已经被取消获取共享状态
static final int CANCELLED = 1;
//后继节点需要被唤醒
static final int SIGNAL = -1;
//节点在condition上等待
static final int CONDITION = -2;
//下一个共享式节点需要被无条件传递
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
//拥有锁的线程
volatile Thread thread;
Node nextWaiter;
}
private transient volatile Node head;
private transient volatile Node tail;
//负责管理线程状态,子类主要通过对state的操作来决定是否获取锁和是否进入队列。
private volatile int state;
}
LockSupport
LockSuppor是构建同步组件的基础工具,它提供了最基本的线程阻塞和唤醒的公共静态方法。
- park():阻塞当前线程,只有调用unpark(thread)或者当前线程中断才会返回。
- unpark(Thread thread):唤醒线程。
Lock和ReentrantLock
JDK1.5后新增的显示锁,可以定时、轮询和中断,如果不需要这些特性尽量选择synchronized。Lock必须保证正常的加锁和解锁,否则会抛出异常。
ReentrantLock可重入锁,默认的确是非公平锁也可以通过构造方法设置为公平锁。ReentrantLock是基于AQS实现的,它的初始state为0表示未锁定状态也只有这个状态锁才是可用状态,当有线程得到锁时state+1,如果同一个线程多次获取锁时state累加,但是每获取一次之后都要释放一次直到state为0的时候锁才释放。
简单使用
public class MyThread extends Thread {
Lock lock = new ReentrantLock();
@Override
public void run() {
//如果加锁出现异常就不会执行unlock()
lock.lock();
try {
System.out.println("MyThread");
} catch (Exception e) {
e.printStackTrace();
} finally {
//保证如果加上了锁,就一定会解锁
lock.unlock();
}
}
}
原理分析
lock
final void lock() {
//修改锁的状态为已占有
if (compareAndSetState(0, 1))
//成功之后就把获得锁的线程设置为自己
setExclusiveOwnerThread(Thread.currentThread());
else
//尝试获取锁
acquire(1);
}
public final void acquire(int arg) {
/**
*调用实现类的尝试获取锁的方法,成功之后直接返回
* 公平锁和非公平锁的区别就在这个方法
*/
if (tryAcquire(arg)) {
return;
}
//获取锁失败之后,入队当前线程
Node node = addWaiter(Node.EXCLUSIVE);
//根据公平性的原则为队列的线程获取锁
if (acquireQueued(node, arg)) {
//让当前线程产生中断
selfInterrupt();
}
}
公平锁和非公平锁tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// c=0 意味着锁是可以占用的状态
if (c == 0) {
/**
* 公平锁
* 如果当前线程是队列的第一个线程,就获取锁,
* 设置锁的状态,和占用锁的线程
*/
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
/**
* 非公平锁
* 不用查询当前线程是否是队列的第一个线程
*/
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
//当前线程拥有锁,由于可重入性,可以使用并且更新锁的状态
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
和synchronized的区别
- synchronized为Java的一个关键字;Lock是一个接口。
- synchronized会自动释放锁,是隐式锁;Lock手动开启和关闭锁,是显示锁
- synchronized时非公平的可重入锁;Lock是可重入锁默认为非公平锁,但是可以修改。
- synchronized不可以中断;Lock支持定时、轮询和中断。
- synchronized无法获取锁的状态;Lock可以得到是否获取锁。
- synchronized有代码块和方法锁;Lock只有代码锁。
Condition
提供了类似对象监视器的方法。
Condition更强大的地方是可以精细的控制多线程的休眠与唤醒。对于同一个锁可以创建多个Condition,在不同的情况下使用不同的Condition。
简单使用
public class MyThread extends Thread {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
boolean flag = false;
@Override
public void run() {
//如果加锁出现异常就不会执行unlock()
lock.lock();
try {
while (flag) {
//等待
condition.await();
}
System.out.println("MyThread");
flag = false;
//类似于notify()
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
//保证如果加上了锁,就一定会解锁
lock.unlock();
}
}
}