摘要
java5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁的功能,它提供了与synchronized关键字类似的同步功能,那为什么还要使用lock锁呢,首先呢lock锁更加灵活,我们可以手动的控制锁,synchronized我们无法控制,只能等结束完成自动释放锁。
分类
Lock有读写锁,读锁是可以共享的,允许多个线程同时读取,也可以叫做共享锁,写锁是独立的,只允许一个线程去写,也叫排它锁,独占锁。
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.readLock();
readWriteLock.writeLock();
Lock分为公平锁和非公平锁,默认为非公平锁,如果想使用公平锁传参数true即可。一般来说非公平锁的效率大于公共锁的效率。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
基本api
LockSupport.park();//是当前线程进入阻塞状态
LockSupport.unpark();//唤醒当前线程
底层原理
获取锁:
final void lock() {
通过cas操作,如果修改值成功,就获取锁成功,并且把当前线程存入 Thread对象
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
没有获取锁的话执行这个,为什么传值1呢,是因为当前原子类的值已经被改为1,如果想获取值成功,只能传1
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
state表示线程是否被持有,0表示没有,1表示有一个,2表示重入次数
protected final boolean tryAcquire(int acquires) {
从thread对象中取出当前线程
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
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;
}
}
主要是为了给node节点的双向链表做关联。
private Node addWaiter(Node mode) {
创建一个node节点,取当前线程
Node node = new Node(Thread.currentThread(), mode);
赋值为tail
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
把队列中额线程设置未阻塞状态
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
短暂自旋
for (;;) {
去上一个节点
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
return interrupted;
}
成立执行下一步
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
调用LockSupport.park使当前线程变为阻塞状态。
private final boolean parkAndCheckInterrupt() {
使当前线程变为阻塞状态
LockSupport.park(this);
return Thread.interrupted();
}
释放锁: 执行释放锁的操作
public final boolean release(int arg) {
如果成立
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
执行该方法
unparkSuccessor(h);
return true;
}
return false;
}
设置state的值并且返回值
protected final boolean tryRelease(int releases) {
状态-1
int c = getState() - releases;
如果不是当前线程抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
线程为null
setExclusiveOwnerThread(null);
}
设置状态为0
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
给当前node节点的状态改为0
compareAndSetWaitStatus(node, ws, 0);
获取node节点的下一个节点
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
唤醒node的下一个节点线程
LockSupport.unpark(s.thread);
}
ReentrantLock总结:
当有线程进入,是否获取锁成功,获取锁成功后,其他其他线程进入,在没有释放锁的情况先,其他线程会放在双向链表的队列中,当释放锁之后,只会唤醒链表的第一个节点,假如此时有其他线程进入时,会判断进入的线程和唤醒链表的头结点的线程是否相同相同获取锁成功,不相同会把新进入的线程添加到链表的尾部,非公平锁则不会,则会根据CPU的调度谁抢到锁算谁的。
ReentrantLock的分析大概就这么多,如有错误欢迎留言指出,谢谢!!!
|