java中的锁还真是挺复杂的,先来一种图看看
上面这些只是常见的分类,根据竞争同步资源的细节其实还分为:无锁,偏向锁,轻量级锁,重量级锁。
最基本的锁实现:Synchronized
乐观锁VS悲观锁
乐观锁就是乐天派,它自信的认为自己能够有机会获取这个资源,没有获取到就不罢休,它非常相信别的线程不会给他加锁,只是检查着某一资源有没有被更改过。比如可以使用CAS算法来实现。
而悲观锁,就非常悲观,如果有线程已经给某一资源加了锁,那么自己就会被阻塞,等待下一次有机会去获取锁。
CAS算法
比如,我们可以看看原子类,一个自增操作。
在AtomicInteger原子类里面,下图所示,非常重要的几个东东
- unsafe:CAS底层实现
- valueOffset:这个数据在内存中的偏移量
- value:我们竞争的Integer资源
然后直接跳到原子类那个递增的方法里面去看看
如果是JDK里面的话就看不到unsafe里面具体内容了,只能有class类,没有源码,在OpenJDK里面看看。
// Unsafe.java
//o是我们传过来的原子类对象,offset是valueOffset,delta就是我们想要修改的差值
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
//先获取到o对象中这个偏移量的值
v = getIntVolatile(o, offset);
//然后while循环就一直比较内存中的值和v是不是一样的
//如果一样,说明没有线程动过这个对象的值,那么就设置为新值v+delta
//不一样你就继续循环咯,知道设置成功
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
private Node addWaiter(Node mode) {
//创建关于当前获取同步状态线程的Node节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//快速尝试在同步队列尾部添加
Node pred = tail;
if (pred != null) {
node.prev = pred;
//compareAndSetHead确保线程被安全添加
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
/*
*enq通过死循环保证节点正确添加
*因为可能在多线程下,很多线程同时去获取同步状态失败,从而被
*加入到同步队列的尾部。
*/
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
//死循环保证正确添加,通过CAS设置尾节点,否则一直尝试
//这样并发的添加节点就变成了串行化
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
自定义DoubleLock共享锁
package current;
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 doubleLock implements Lock{
private final Sync sync = new Sync(2);
private static final class Sync extends AbstractQueuedSynchronizer{
public Sync(int count) {
// TODO Auto-generated constructor stub
if( count <= 0) {
throw new IllegalArgumentException("count must be larger than zero");
}
setState(count);
}
@Override
protected int tryAcquireShared(int arg) {
// TODO Auto-generated method stub
for(;;) {
//死循环
int current = getState();
int newCount = current - arg;
//只有当这个newcount大于0,才能算外面的线程获得到了锁
if(newCount < 0 || compareAndSetState(current,newCount)) {
return newCount;
}
}
}
@Override
protected boolean tryReleaseShared(int arg) {
// TODO Auto-generated method stub
for(;;) {
int current = getState();
int newcount = current + arg;
if(compareAndSetState(current, newcount)) return true;
}
}
}
public void lock() {
sync.acquireShared(1);
}
@Override
public void unlock() {
// TODO Auto-generated method stub
sync.releaseShared(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
// TODO Auto-generated method stub
}
@Override
public boolean tryLock() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
// TODO Auto-generated method stub
return false;
}
@Override
public Condition newCondition() {
// TODO Auto-generated method stub
return null;
}
}
package current;
import java.util.concurrent.locks.Lock;
public class doubleLockTest {
public static void main(String[] args) {
doubleLockTest pDoubleLockTest = new doubleLockTest();
pDoubleLockTest.test();
}
public void test() {
final Lock lock = new doubleLock();
class Worker extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
lock.lock();
System.out.println(Thread.currentThread().getName());
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
for(int i=0;i<10;i++) {
Worker worker = new Worker();
worker.setDaemon(true);//守护进程
worker.start();
}
for(int i=0;i<10;i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("换一行");
}
}
}
LockSupport工具
在进入AQS和其他Lock相关内容之前,我觉得有必要了解一下LockSupport工具,不然都不知道代码在干嘛。
//唤醒操作
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
public static void parkNanos(Object blocker, long nanos) {
if (nanos > 0) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, nanos);
setBlocker(t, null);
}
}
//阻塞直到deadline,加入一个blocker对象,可以有信息访问
public static void parkUntil(Object blocker, long deadline) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(true, deadline);
setBlocker(t, null);
}
//阻塞进入waiting状态
public static void park() {
UNSAFE.park(false, 0L);
}
//阻塞nanos纳秒
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
//知道某个时间界限才唤醒
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
Lock接口和AQS源码分析
独占式AQS
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//先用自己定义的tryAcquire逻辑尝试获取同步状态
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//获取失败就调用addWaiter将线程加入同步队列,并调用acquireQueued去自旋获取锁
selfInterrupt();//如果获取失败而且自己曾被中断过,那么就自我中断
}
addWaiter方法
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 这里尝试快速插入队列,如果失败了就是用enq方式插入即CAS自旋
Node pred = tail;
if (pred != null) {
//快速插到尾部
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//调用enq方法将节点CAS自旋插入同步队列中
enq(node);
return node;
}
//将这个节点以自旋的方式插入到其中
private Node enq(final Node node) {
for (;;) {
//死循环,一种到插入到队列中成功
//底层也是用的unsafe的CAS操作
Node t = tail;
if (t == null) {
//空队列,插入
if (compareAndSetHead(new Node()))
tail = head;
} else {
//非空插入
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
acquireQueued方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循环自旋的方式
for (;;) {
//获取前驱节点
final Node p = node.predecessor();
//如果前驱节点为头节点而且去尝试以独占锁的方式获取锁;tryAcquire的方法要具体的实现类去实现,获取成功返回true,否则返回false;
if (p == head && tryAcquire(arg)) {
//前驱节点确实为头节点,此时头节点可能还拿着锁,如果头节点释放了,当前节点确实可以尝试获取成功了,那么设置头节点
setHead(node);
p.next = null; // 将原来的节点置为null,便于垃圾回收
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//获取失败,直接取消获取
if (failed)
cancelAcquire(node);
}
}
//acquireQueued继续调用shouldParkAfterFailedAcquire方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//拿到前驱的状态
if (ws == Node.SIGNAL)
//如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了
return true;
if (ws > 0) {
/*
* 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。
* 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被保安大叔赶走了(GC回收)!
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢!
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//调用parkAndCheckInterrupt检查自己是不是中断过
private final boolean parkAndCheckInterrupt() {
2 LockSupport.park(this);//调用park()使线程进入waiting状态
3 return Thread.interrupted();//如果被唤醒,查看自己是不是被中断的。
4 }
Release释放锁
释放因为只有一个头节点,不存在线程安全问题,可以直接释放,不需要加锁之类的。既不需要并发去维护这个同步队列的锁释放
public final boolean release(int arg) {
2 if (tryRelease(arg)) {
3 Node h = head;//找到头结点
4 if (h != null && h.waitStatus != 0)
5 unparkSuccessor(h);//唤醒等待队列里的下一个线程
6 return true;
7 }
8 return false;
9 }
private void unparkSuccessor(Node node) {
//这里,node一般为当前线程所在的结点。
int ws = node.waitStatus;
if (ws < 0)//置零当前线程所在的结点状态,允许失败。
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;//找到下一个需要唤醒的结点s
if (s == null || s.waitStatus > 0) {//如果为空或已取消
s = null;
for (Node t = tail; t != null && t != node; t = t.prev) // 从后向前找。
if (t.waitStatus <= 0)//从这里可以看出,<=0的结点,都是还有效的结点。
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//唤醒
}
上面就是独占方式的AQS,可以实现那个重入锁ReetranLock
共享式AQS
重入锁ReentranLock源码分析
上面有了AQS的独占式,就可以直到重入锁的ReentranLock的实现了,重入锁只需要重写lock,和unlock,以及tryAcquire和一些必要方法。主要是在tryAcquire里面实现重入的逻辑。想一想,我们是因为状态>0才不能继续获取锁,但是如果价格判断,如果获取锁的是当前自己,那不就可以实现重入了吗。
protected final boolean tryAcquire(int acquires) {
//获取当前线程
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;
}