Java并发编程——ReentrantLock重入锁解析

重入锁

所谓重入锁,即支持重入性,表示能够对共享资源重复加锁,即当前线程获取该锁再次获取不会被阻塞。

重入性

在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功; 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。

ReentrantLock分为公平锁与非公平锁,默认选择的是非公平锁(无参构造函数),另一种构造方式,可传入一个boolean值。true时为公平锁。

以非公平锁为例,来看看它是如何获取锁。核心方法为其内部的抽象静态类Sync中的nonfairTryAcquire

/**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //state为0表示该锁没有被任何线程占有,可以被获取
            if (c == 0) {
                //如果线程当前状态值accquires为0
                if (compareAndSetState(0, acquires)) {
                   //设置当前拥有独占访问权限的线程。
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果线程被占有,检查占有线程是否为当前线程
            else if (current == getExclusiveOwnerThread()) {
                //再次获取锁,计数加1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //如果被其他线程占有,则获取锁失败
            return false;
        }

每次重新获取锁都会对同步状态加1操作,那么释放锁相对的,就会进行减一操作,释放锁核心方法tryRelease

protected final boolean tryRelease(int releases) {
             //同步状态减1
            int c = getState() - releases;
           //如果当前线程,不是锁占有线程,抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //只有当同步状态c等于0,锁才能被成功释放。
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //c不等于0,锁释放失败 返回false
            setState(c);
            return free;
        }

每次锁重新获取都会对同步状态进行加一的操作,同样释放锁tryRelease执行减一操作,必须等到同步状态为0时,锁才算成功释放。也就是锁被获取了n次,只有释放n次才算成功释放。

公平锁与非公平锁

ReentrantLock支持两种锁,公平锁与非公平锁。
ReentrantLock的构造函数可以传递一个boolean值,true时为公平锁,false为非公平锁。

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

所谓公平锁,就是锁的获取顺序符合请求上的绝对时间顺序,满足FIFO(先入先出)。

ReentrantLock还有无参构造函数,为非公平锁。

public ReentrantLock() {
        sync = new NonfairSync();
    }

上面讲了非公平锁的nonfairTryAcquire方法,这里看看公平锁的获取逻辑,tryAcquire方法。

 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;
        }
    }

上述代码可以看出,与非公平锁的区别就在多了一个hasQueuedPredecessors()的判断。该方法是用来判断当前节点在队列中是否有前驱节点。如果有,说明有线程比当前线程更早的请求资源,根据先入先出原则,所以当前线程请求锁失败。

public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        //当前队列不为空,当前节点不是头节点,当前节点的线程不是当前线程
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

公平锁&非公平锁

公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序,而非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成“饥饿”现象。

公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,ReentrantLock默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。

一个小栗子

package com.lw.study.thread;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: 
 * @Date: 16:46 2018/8/30
 */
public class LockDemo extends Thread{
    private static ReentrantLock lock = new ReentrantLock();
    private static int count = 0;

    @Override
    public void run() {
        try {
            lock.lock();
            for (int i = 0; i < 5; i++) {
                System.out.println("ThreadName : " + Thread.currentThread().getName() +" : " +count);
                count ++;
            }
        }finally {
          lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        LockDemo lockDemo = new LockDemo();
        LockDemo lockDemo2 = new LockDemo();
        lockDemo.start();
        lockDemo2.start();
        lockDemo.join();
        lockDemo2.join();
        System.out.println(count);
    }
}

输出结果

hreadName : Thread-0 : 0
ThreadName : Thread-0 : 1
ThreadName : Thread-0 : 2
ThreadName : Thread-0 : 3
ThreadName : Thread-0 : 4
ThreadName : Thread-1 : 5
ThreadName : Thread-1 : 6
ThreadName : Thread-1 : 7
ThreadName : Thread-1 : 8
ThreadName : Thread-1 : 9
10

可以看到,在Thread-0执行完后,Thread-1才开始执行。如果不加锁,结果将是两个线程交替随机执行。

ThreadName : Thread-0 : 0
ThreadName : Thread-0 : 1
ThreadName : Thread-1 : 0
ThreadName : Thread-0 : 2
ThreadName : Thread-1 : 3
ThreadName : Thread-0 : 4
ThreadName : Thread-1 : 5
ThreadName : Thread-0 : 6
ThreadName : Thread-1 : 7
ThreadName : Thread-1 : 9
10

你可能感兴趣的:(学习笔记)