天天看点

线程---重入锁(ReentrantLock)

重入锁(ReentrantLock)

 锁是用来控制多个线程访问共享资源的方式。

 一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而Java SE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。

  • 重入锁:

    支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁还支持获取锁时的公平和非公平性选择。

  • 源码探究:
public class ReentrantLock implements Lock, java.io.Serializable
           
  • 方法:
void lock():加锁
void lockInterruptibly() throws InterruptedException:可中断锁
boolean tryLock():尝试性的进行加锁
boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException: 在有限时间内进行尝试性加锁
void unlock():释放锁
Condition newCondition():通信类

           
  • 构造函数:
无参构造,默认采用非公平性锁
public ReentrantLock() {
        sync = new NonfairSync();
    }
    
有参构造,参数为Boolean,true:公平性锁,false:非公平性锁
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

           

AbstractQueuedSynchronizer

基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。

该同步器利用了一个int来表示状态。

同步器的开始提到了其实现依赖于一个FIFO队列,

那么队列中的元素Node就是保存着线程引用和线程状态的容器,

每个线程对同步器的访问,都可以看做是队列中的一个节点。

Node的主要包含以下成员变量:

Node {
	int waitStatus;//节点的状态
    Node prev;//前驱节点
    Node next;//后继节点
    Node nextWaiter;//存储condition队列中的后继节点
    Thread thread;//入队列时的当前线程
}

表示节点的状态。其中包含的状态有:
CANCELLED,值为1,表示当前的线程被取消;
SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
值为0,表示当前节点在sync队列中,等待着获取锁。
           

AQS(AbstractQueuedSychronizer—-Node组成的链表)中的state:

state=0:表示锁是空闲状态;

state>0:表示锁被占用;

state<0:表示溢出;

state > 1 : 当前获取锁的次数。

重入锁的实现:

当前线程每获取一次锁就进行+1操作,每次释放锁对state减一,当state=0时才是真正的释放锁。

  • ReentrantLock方法列举:
ReentrantLock方法列举:

int getHoldCount():返回当前锁获取的次数
boolean isHeldByCurrentThread():
boolean isLocked()
Thread getOwner()
boolean hasQueuedThreads()
boolean hasQueuedThread(Thread thread):判断传入的线程是否处于阻塞对列中
int getQueueLength()
Collection<Thread> getQueuedThreads()
boolean hasWaiters(Condition condition)
int getWaitQueueLength(Condition condition)
    
    ReentrantLock底层实现有三个内部类:FairSync、NonfairSync、Sync
    
     abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        abstract void lock();
获取锁相关方法:
        final boolean nonfairTryAcquire(int acquires) {
        //获取当前线程
            final Thread current = Thread.currentThread();
		//获取锁的状态
            int c = getState();
            if (c == 0) {
				//表示当前锁空闲,通过CAS获取锁,获取成功则将state置为1 ,并且将当前线程记录下来,直接返回
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
				//表示锁不是空闲,当前线程获取的锁
                int nextc = c + acquires;
                if (nextc < 0) {// overflow 获取锁次数到达上限,直接抛出异常
                    throw new Error("Maximum lock count exceeded");
				}
                setState(nextc);//若没有到达上限,则直接更新state,直接返回,加一操作
                return true;
            }
			//当前获取锁的线程不是当前线程
            return false;
        }

		//尝试释放锁
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
			//获取锁的线程非当前线程,则直接抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
				//当state减为1时,才真正释放锁,将持有锁的线程置为null
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
		
           
  • 简单实现方法:
public class ReentrantLock0416 {
    public static void main(String[] args) {
        //ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); //读写锁
        ReentrantLock reentrantLock = new ReentrantLock();

       /* *//**默认不公平性锁
         * Creates an instance of {@code ReentrantLock}.
         * This is equivalent to using {@code ReentrantLock(false)}.
         *//*
        public ReentrantLock() {
            sync = new ReentrantLock.NonfairSync();
        }*/
        ReentrantLock reentrantLock1 = new ReentrantLock(true);//公平性锁
        reentrantLock.lock();
        System.out.println("重入锁");
        reentrantLock.unlock();//加锁更细 显性实现加锁

        //线程通信
        Condition condition = reentrantLock.newCondition();
        condition.signal();//类似notify()
        condition.signalAll();//notifyAll()
        try {
            condition.await();//wait()
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            reentrantLock.lockInterruptibly();//中断加锁
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

           
  • 公平性锁和非公平性锁:

    锁获取的公平性问题,如果在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。

**NonFairSync:**非公平性锁

第一次通过CAS强制抢锁,抢锁失败则再次尝试性抢锁,

尝试性抢锁:首先判断锁是否被占用(state=0),是则直接CAS来获取锁,

获取成功则修改 state,且将当前线程计入AQS中,成功返回。

在判断占用的情况下,占用锁的线程是否是当前线程,则直接修改state,成功返回。

以上都不满足,失败返回,若失败返回,则当前线程未获取到锁,则将线程加入到AQS中。


    /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
       
        final void lock() {
            //CAS强制获取锁
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //尝试性抢锁
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
  //获取锁的操作
        final boolean nonfairTryAcquire(int acquires) {
        //获取当前线程
            final Thread current = Thread.currentThread();
		//获取锁的状态
            int c = getState();
            if (c == 0) {
				//表示当前锁空闲,通过CAS获取锁,获取成功则将state置为1 ,并且将当前线程记录下来,直接返回
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
				//表示锁不是空闲,当前线程获取的锁
                int nextc = c + acquires;
                if (nextc < 0) {// overflow 获取锁次数到达上限,直接抛出异常
                    throw new Error("Maximum lock count exceeded");
				}
                setState(nextc);//若没有到达上限,则直接更新state,直接返回,加一操作
                return true;
            }
			//当前获取锁的线程不是当前线程
            return false;
        }



**FairSync:**公平性锁

尝试性获取锁,若锁未被占用,判断当前现场是否满足条件(AQS队列为空且当前线程处于AQS队列的队头),修改state,若锁被占用,且是当前线程占用锁,修改state,成功返回。

若以上都不满足,则失败返回。

若未获取到锁,则即将线程加入到AQS中的队列。


    static final class FairSync extends Sync {
        
        final void lock() {
            acquire(1);
        }
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //队列为空并且当前线程 处于队列的first的node
                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;
        }
    }
    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());
		    //队列不为空
            //且队列里只有一个结点或者头结点所在的线程不等于当前的线程
    }


           
  • 公平性锁和不公平性锁的区别:
  1. 锁的获取顺序:
  • 公平性与否是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO。
  • 而非公平性锁获取锁是抢占式的,当一个线程获取锁后再释放,有很大的概率会再次获取到锁,因为刚释放锁的线程会再次获取到同步状态的几率会非常大,使得其他线程只能在同步队列中等待。
  1. 主要在Lock()方法实现上不同:
  • 抢锁次数不同;非公平性锁进行两次抢夺,公平性锁进行一次抢夺;
  • 公平性锁在抢锁时判断是否处于队头;