java高并发学习-ReentrantLock源码走读(1)

目前在学习《java并发编程的艺术》这本书,有好多东西都是一知半解,于是决定读一下各个锁实现的源码,加深对锁获取释放以及线程中断的理解。在此做个笔记。

先说线程的中断,贴上部分源码。

​
//线程的interrupt()方法源码,即new Thread().interrupt()
public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

//interrupt0()方法源码
private native void interrupt0();//该方法是一个native方法,暂且将其认为是jvm来实现的
线程被中断后,会设置一个标志位代表线程被中断。
//Thread.interrupted()源码
 public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
再往下看,我们发现isInterrupted方法也是一个native方法
private native boolean isInterrupted(boolean ClearInterrupted);


​

由上述源码我们可以看出,加入新建一个线程,然后对其进行中断,则jvm会执行相关操作对该线程设置一个中断标志位,Thread的interrupted()也只是返回该标志位,然后isInterrupted根据传入的boolean变量ClearInterrupted来判断是否在返回当前中断标志之后擦出中断标志。

我之前一直有一个问题:这个线程的中断到底是干什么用的,为什么wait,sleep以及Condition中的await是响应中断的方法,响应中断和不响应中断有什么区别。下面结合源码的走读说一说我自己的理解。

1.回顾一下synchronized

我们都知道synchronized是独占锁,在同步块中可以使用wait以及notify来实现等待和通知的机制。此处解释一个一直以来困扰我自己的问题,一个线程wait后,如果被打断,那打断之后wait到底是如何执行的。下面直接上代码:

//随便写一个用来构造线程的类,用来走读源码
public class Test {
	
	private Map<?,?> map = new HashMap<String,Object>();
	public void testsyn() {
		synchronized (map) {
			try {
				wait();// ----1
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
//wait方法
public final void wait() throws InterruptedException {
        wait(0);//-------2
    }
//wait(long timeout)
public final native void wait(long timeout) throws InterruptedException;//------3

线程执行wait之后其实是执行第3步的native方法,通过查阅资料得知:若此时线程被中断,则wait方法中会根据线程的中断状态来判断是否抛出异常,如果线程未被中断,则从wait方法返回时一定获取了锁,否则线程一直阻塞在wait方法内(之前此处解释有误,如如果线程被中断,则抛出异常,线程中相应的执行逻辑不会执行,走到catch中然后finally后结束)。线程被中断后返回,此时带回InterruptedException,并在上述代码 1处抛出,再由trycatch检测到,在catch中可以加入一些自定义的逻辑。

2.ReentrantLock源码走读

还是直接上代码,在代码中一步步进行解析:

//-------------1
public static void main(String[] args){
        //一般我们再使用的时候都是直接new一个ReentrantLock对象然后调用他的lock方法
		ReentrantLock lock = new ReentrantLock();//默认使用非公平的获取方式
        lock.lock(); 
		Condition cd = lock.newCondition();
		try {
			cd.await();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
//-------------2.ReentrantLock()的构造函数:
//默认非公平
public ReentrantLock() {
        sync = new NonfairSync();
    }

//可选公平
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
先分析非公平锁的获取:
//------------3.NonfairSync是Sync的一个实现类,juc下的锁都是通过在类内部实现AbstractQueuedSynchronizer并自定义实现相应的模板方法,是典型的模板方法模式。

//NonfairSyn中的两个方法,从lock方法开始一步步进入源码。
final void lock() {
            if (compareAndSetState(0, 1))//-----首先尝试修改同步状态,如果修改成功,证明获取到锁,由lock方法返回,此时流程结束。
                setExclusiveOwnerThread(Thread.currentThread());
            else//如果没有获取到,则证明存在竞争,进入acquire方法。
                acquire(1);
        }

protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
//--------4 acquire方法是AbstractQueuedSynchronizer提供的方法,它会调用子类的tryAcquire方法,此处即NonfairSync中的protected final boolean tryAcquire(int acquires)
public final void acquire(int arg) {
        //tryAcquire方法用来获取锁,若成功则由方法返回,然后acquire方法返回,流程结束。如果不成功,则用当前线程信息构造节点加入同步队列,此处Node.EXCLUSIVE代表独占式获取。
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
//------5先看tryAcquire方法,即调用的nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {  //-----如果同步状态为0,则证明此时没有其他线程成功获取到锁,然后尝试获取,获取成功返回true。流程结束,一直返回返回到最初使用lock()方法的地方,即获取锁成功。
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
//如果同步状态不为0,证明有线程获取到了同步状态,此时判断是否是当前线程已经获取了同步状态,如果是则增加同步状态,当前线程再次获取锁成功。这也是ReentrantLock支持锁重入的实现。
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
//有其他线程获取了同步状态,nonfairTryAcquire也是成功返回,返回false。
            return false;
        }

nonfairTryAcquire方法返回之后,再翻上去看4步骤中的判断条件:!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg),注意tryAcquire前面的非以及判断条件中短路运算的用法。

获取锁失败时,需要构造当前线程的节点并加入同步队列,注意此时还在acquire方法的执行流程中。继续上代码,接上面的代码部分:

//------6构造节点,这部分代码没有走读,参考《并发编程艺术中的解释》
private Node addWaiter(Node mode) {
        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;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);//保证正确将节点添加至同步队列
        return node;
    }
//-------7。节点构造成功并加入同步队列后执行acquireQueued方法开始获取同步状态
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; // help GC
                    failed = false;
                    return interrupted;//此时线程是由前驱释放同步状态来唤醒的,所以中断标志为false。acquireQueued方法返回,一直到最上层acquire方法返回,此时成功获取到锁。否则执行下面代码。
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
//--------8.shouldParkAfterFailedAcquire//判断当前线程是否找到合适的休息点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)//已经找到,即当前节点可以被前驱唤醒。回到if判断条件,此时当前线程可以进入阻塞状态,即等待前驱的唤醒。
            return true;
        if (ws > 0) {
//如果前驱节点中的线程已经被取消,则继续查找状态为signal的节点并将此节点设置为当前节点的前驱,设置完之后shouldParkAfterFailedAcquire返回为false。继续7中的for循环来获取锁。
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
//-------9 parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//park方法用于阻塞当前线程
        return Thread.interrupted();
    }
//locksupport中的park方法
public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
//执行park方法之后,线程阻塞需要等待前驱的唤醒或者中断,个人理解public native void park(boolean paramBoolean, long paramLong);方法在被唤醒或者中断后才从该方法返回,一步一步直接返回到7中继续获取锁。否则,acquire方法一直没有返回,即线程阻塞等待。如果是被中断唤醒则将中断标志位记录并返回。

最后执行selfInterrupt将中断状态补上,以便后续的自定义操作。

关于acquireQueued中的cancelAcquire方法,结合书中:从lock方法返回一定是获取到了锁。个人的理解是,如果cancelAcquire得到执行,则证明当前线程在获取锁的过程中出现了异常或者错误,该错误是关于线程本身的一些错误,导致线程蹦掉了,此时线程中的任务不会执行,即使获取锁失败最终从lock方法返回也不会有问题,因为当前线程已经挂了。

由上述走读可知,lock方法是不支持中断的。下一篇将继续走读Condition中的await方法以及响应中断获取锁的方法。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值