高并发学习笔记(六)
2019-03-26 11:21
141 查看
二、同步器的实现(二)
3.ReentrantLock
ReentrantLock是个可重入的互斥锁,具有与使用synchronized同步代码访问monitor对象相同的一些基本行为和语义,但是ReentrantLock更加强大并且效率要高一些。下面看个示例:
/** * ReentrantLock示例 * Created by bzhang on 2019/3/17. */ public class TestReentrantLock { private ReentrantLock lock = new ReentrantLock(); public void m1(){ lock.lock(); //获取锁,若锁被别的线程占用则阻塞等待获取锁 try { System.out.println(Thread.currentThread().getName()+"开始占着茅坑了"); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+"占了3小时的茅坑"); } catch (InterruptedException e) { e.printStackTrace(); }finally { //lock.unlock(); //解锁,锁用完后一定要解锁,不然其他线程无法获取到锁,会一直阻塞 } } public void m2(){ System.out.println(Thread.currentThread().getName()+"也想用茅坑,但被占着了"); lock.lock(); try { System.out.println(Thread.currentThread().getName()+"终于等到了"); }finally { lock.unlock(); } } public static void main(String[] args) { TestReentrantLock test = new TestReentrantLock(); ExecutorService pool = Executors.newFixedThreadPool(2); pool.submit(new Runnable() { @Override public void run() { test.m1(); } }); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } pool.submit(new Runnable() { @Override public void run() { test.m2(); } }); pool.shutdown(); } }
知道了简单的用法,我们来看看ReentrantLock,其继承关系如下图,实现了Lock接口。
Lock接口中有如下待实现方法:
public interface Lock { //获取锁,若锁不可用(拿不到锁),就让当前线程休眠 void lock(); //获取锁,但是在等待获取时可被中断 void lockInterruptibly() throws InterruptedException; //尝试获取锁,即锁当前未被其它线程占用,则获取锁返回true,若锁被占用,不等待直接返回false boolean tryLock(); //在一定时间内尝试获取锁,若在规定时间内成功获取就返回true,否则就返回false。 boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //解锁/释放锁,持有锁的线程才能释放锁,因此使用完锁一定要释放,不然会造成死锁。 void unlock(); //返回绑定到此锁的condition实例 Condition newCondition(); }
接口看完,来看看ReentrantLock是如何实现的,先看看构造方法:
private final Sync sync; //同步队列 //空构造 public ReentrantLock() { sync = new NonfairSync(); //创建一个非公平的同步队列,是AQS的子类实现 } //根据fair构造 public ReentrantLock(boolean fair) { //根据fair创建一个公平或非公平同步队列 sync = fair ? new FairSync() : new NonfairSync(); }
由构造方法可知,ReentrantLock实际是创建了一个同步队列,NonfairSync或者FairSync。他们都是AQS的实现类,实现了AQS的独占模式,其中NonfairSync是非公平模式下竞争锁资源,即线程竞争锁资源时不以等待时间长短来决定。而FairSync则是公平模式下获取锁资源,即按照线程等待时间的长短来决定洗个获取锁资源的是谁,其继承关系如下图:
下面先看看公平同步队列及非公平同步队列的实现源码:
//ReentrantLock中FairSync的实现 //公平同步队列实现类 static final class FairSync extends Sync { //尝试获取锁资源,若未获取成功则进入同步队列中等待 final void lock() { //不可被中断的尝试获取锁方法 //前面分析AQS时已经分析过了,此处只要知道在进入等待队列之前acquire方法会调用 //tryAcquire方法先尝试获取锁即可,若获取锁失败则会加入同步队列中等待(因为acquire中调用了acquireQueued方法) acquire(1); } //尝试获取锁,AQS实现类必须重写的方法。 protected final boolean tryAcquire(int acquires) { //获取当前线程 final Thread current = Thread.currentThread(); int c = getState(); //获取AQS同步队列状态值 //判断同步状态值是否为0,为0即锁资源当前处于释放状态,竞争锁 //不为0表示已经有线程获取到锁,直接去判断当前线程是否是拥有锁的线程 if (c == 0) { //hasQueuedPredecessors是AQS中的方法,查询是否存在比当前线程等待时间更久的线程 //若不存在,表示当前线程的等待时间就是最长的,那么就尝试更新同步队列的状态值为acquires //若当前线程是等待最久且同步队列状态值成功更新为acquires,那么就将当前线程设置为拥有独占访问的线程(即获取到锁资源) 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; } } //非公平同步队列的实现类 static final class NonfairSync extends Sync { //尝试获取锁资源,若未获取成功则进入同步队列中等待 final void lock() { //直接尝试设置同步状态值,若成功表示获取到锁资源,直接将当前线程设为拥有锁资源的线程 //设置不成功在去尝试获取锁 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } //尝试获取锁操作,调用父类的nonfairTryAcquire方法 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } } //NonfairSync及FairSync的公共父类 abstract static class Sync extends AbstractQueuedSynchronizer { abstract void lock(); //抽象获取锁方法,子类实现,若为获取到锁,则进入同步队列等待 //非公平的尝试获取锁对象 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); //当前线程 int c = getState(); //获取同步队列的状态值 //判断状态值是否为0,即锁是否未被其他线程获取 if (c == 0) { //直接尝试更新状态值,若是成功(即获取到锁)直接返回 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); return true; } return false; } //尝试释放锁资源 protected final boolean tryRelease(int releases) { int c = getState() - releases; //获取当前队列的状态值,并得到想要更新的状态值 //判断当前线程是否是拥有锁资源的线程,不是抛异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //释放锁是否成功的标识 //若更新的状态值为0,表示线程将释放锁 //不为0,表示是重入锁,尚未到达释放状态 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); //更新状态 return free; } //判断当前线程是否是拥有锁资源的额线程 protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } //新建条件队列 final ConditionObject newCondition() { return new ConditionObject(); } //返回当前独占锁资源的线程,若锁已被释放则返回null final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } //返回同步状态值,若大于0锁资源处于独占状态,且可表示重入的次数。 //返回为0表示处于释放状态 final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } //判断锁资源是否处于占用状态 final boolean isLocked() { return getState() != 0; } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } }
以上就是ReentrantLock中公平锁及非公平锁底层的同步队列的实现,下面看看ReentrantLock的源码:
public class ReentrantLock implements Lock, java.io.Serializable { //尝试获取锁方法,锁锁资源已被其他线程占用,则阻塞当前线程以等待获取锁 //实际是调用底层同步队列的lock方法 public void lock() { sync.lock(); //实际调用的获取锁方法 } //可被中断的尝试获取锁的方法 public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); //调用AQS中的方法实现 } //尝试获取锁,以非公平方式尝试获取锁,只尝试一次,获取不到不会等待直接返回 public boolean tryLock() { return sync.nonfairTryAcquire(1); } //在一定时间内尝试获取锁,若在timeout时间内成功获取,返回true,超时则返回false public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } //释放锁,必须要拥有锁才能释放,不然抛异常 public void unlock() { sync.release(1); } //返回与当前锁资源相关的条件队列 public Condition newCondition() { return sync.newCondition(); //实际是新建一个条件等待队列 } //查看拥有锁的线程的重入次数 public int getHoldCount() { return sync.getHoldCount(); } //判断当前线程是否是拥有锁的线程 public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } //查看锁是否被占用(即被某一线程使用) public boolean isLocked() { return sync.isLocked(); } //查看是否是公平锁 public final boolean isFair() { return sync instanceof FairSync; } //获取当前拥有锁的线程,若锁资源处于释放状态,则返回null protected Thread getOwner() { return sync.getOwner(); } //查看是否有线程等待获取锁,即同步队列是否为空 public final boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } //查看thread线程是否是等待获取锁的一员 public final boolean hasQueuedThread(Thread thread) { return sync.isQueued(thread); } //查看等待获取锁的线程数的估计值 public final int getQueueLength() { return sync.getQueueLength(); } //获取同步队列中等待获取锁的线程列表 protected Collection<Thread> getQueuedThreads() { return sync.getQueuedThreads(); } //condition等待队列中是否有等待条件的结点 public boolean hasWaiters(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition); } //获取给定的condition中等待队列的长度的估计值 public int getWaitQueueLength(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition); } //获取给定condition条件队列中等待线程的线程列表 protected Collection<Thread> getWaitingThreads(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition); } }
相关文章推荐
- Go语言并发与并行学习笔记(一)
- 学习笔记(九)并发(三)
- Go语言并发与并行学习笔记(一)
- Java并发学习笔记(八)-LinkedBlockingQueue
- Go语言学习笔记-并发
- 并发编程学习笔记之Lock与synchronized
- Java并发学习笔记(15)信号量(Semaphore) 关卡((2)CyclicBarrier)
- 学习笔记:java并发编程学习之初识Concurrent
- JAVA并发编程学习笔记之ReentrantLock (r)
- MySQL学习笔记之四:并发控制和事务机制
- Java并发编程学习笔记 深入理解volatile关键字的作用
- 学习JAVA多线程编程 --- 《JAVA多线程编程核心技术》第2章 对象及变量的并发访问 笔记
- WCF学习笔记之并发与限流
- Java并发读书学习笔记(九)——性能与可伸缩性
- Java并发读书学习笔记(十)——显式锁
- Java学习笔记—多线程(并发工具类)
- Java并发读书学习笔记(十一)——原子变量与非阻塞同步机制
- 并发编程实战学习笔记(七)——避免活跃性问题
- 并发编程实战学习笔记(八)——性能与可伸缩性
- 并发学习笔记(三):join与wait/notify