Java-锁:ReentrantLock底层原理

本文详细介绍了Java中的可重入锁ReentrantLock,包括它的可重入特性、与synchronized的区别、公平锁与非公平锁的概念,以及加锁流程。ReentrantLock提供了等待可中断、公平锁选择和绑定多个条件等高级功能。

ReentrantLock

1.什么是可重入锁?

即一个线程持有某个对象的锁时,再次去获取这个对象的锁可以成功的。

ReentrantLock是个典型的独占模式AQS,同步状态为0时表示空闲。当有线程获取到空闲的同步状态时,它会将同步状态加1,将同步状态改为非空闲,于是其他线程挂起等待。在修改同步状态的同时,并记录下自己的线程,作为后续重入的依据,即一个线程持有某个对象的锁时,再次去获取这个对象的锁是可以成功的。

2.ReentrantLock的特点

ReentrantLock锁相对于Synchronized锁的高级功能,

  1. 等待可中断:指当前持有所得线程长期不释放锁的时候,等待的线程可以选择放弃等待,改为处理其他事情。

将lockInterruptibly() 放到代码块中,调用interrupt方法进行中断时,lockInterruptibly()会立即响应。

        @ReservedStackAccess
        final void lockInterruptibly() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            if (!initialTryLock())
                acquireInterruptibly(1);
        }
  1. 公平锁:指多个线程在等待同一锁时,当锁可用时,必须按照申请锁的时间顺序来依次获取锁。
  2. 锁绑定多个条件:相对于synchronized 的object.notify() , object.notifyAll() 只能随机唤醒一个,或者全部唤醒。
    ReentrantLock唤醒线程时可以有选择的唤醒。
public void threadDemo2() {

		Lock lock = new ReentrantLock();
		Condition c1 = lock.newCondition();
		Condition c2 = lock.newCondition();
		

		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				lock.lock();
				for (int i = 'A'; i < 'K'; i++) {

					while (state != 1) {
						try {
							c1.await();				
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					System.out.print((char) i + " ");
					state = 2;
					c2.signal();
				}
				lock.unlock();

			}
		}).start();

		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				lock.lock();
				for (int i = 1; i < 11; i++) {

					while (state != 2) {
						try {
							c2.await();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					System.out.print( i + " ");
					state = 1;
					c1.signal();
				}
				lock.unlock();

			}
		}).start();

	}

3.公平锁和非公平锁是什么?有什么区别?

公平锁:就是当锁可用时,等待锁时间最长的线程获得锁。
非公平锁:当锁可用时,随机分配使用权

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
 }
  • 当使用的是非公平锁时,只要CAS操作成功,即获取锁
        /**
         * Acquire for non-reentrant cases after initialTryLock prescreen
         */
        protected final boolean tryAcquire(int acquires) {
            if (getState() == 0 && compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
  • 当使用非公平锁时,在进行CAS操作之前,还判断当前线程是否有前驱节点,有的话返回false
        /**
         * Acquires only if thread is first waiter or empty
         */
        protected final boolean tryAcquire(int acquires) {
            if (getState() == 0 && !hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

4. 加锁流程

请添加图片描述

  1. 首先调用lock()加锁,
  2. 执行初始加锁,initialTryLock()
    2.1 如果state = 0,并且CAS操作成功,那么设置保存当前线程号,加锁成功,
    2.2 否则看当前拥有锁的线程是否是自己,是的话,state++,即重入
    2.3 否则加锁失败,调用acquire(1); 执行入队流程
  3. 队列中的线程自旋尝试再次获取锁时,tryAcquire();
    3.1 使用公平锁,非公平锁时的判断条件不一样,如上所示
    1.ReentrantLock:
    public void lock() {
        sync.lock();
    }
    2. sync      
    final void lock() {
     if (!initialTryLock())
         acquire(1);
   }
   3. FairSync/NonfairSync
      final boolean initialTryLock() {
          .....
      }
   4. sync
    public final void acquire(int arg) {
        if (!tryAcquire(arg))
            acquire(null, arg, false, false, false, 0L);
    }
    5. FairSync/NonfairSync 
  //自旋获取锁,
  //这一个方法FairSync/NonfairSync的不同重写,体现了Fair、NoFair的特点
   protected final boolean tryAcquire(int acquires) {
         ......
  }
    6.sync
    //主要的请求方法,等待队列的管理,node入队的操作。。。
    final int acquire(Node node, int arg, boolean shared,
                      boolean interruptible, boolean timed, long time) {
        .......


    }

总结

  1. ReentrantLock与Synchrionized的区别
  1. 底层实现上来说,
  • synchronized 是JVM层面的锁,是Java关键字,通过monitor对象来完成(monitorenter与monitorexit),锁的竞争其实就是对monitor对象的竞争。
    synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、轻量级锁、重量级锁。
  • ReentrantLock 是API层面的锁,ReentrantLock实现则是通过利用CAS(CompareAndSwap)保证线程操作的原子性,volatile保证数据可见性以实现锁的功能。
  1. 加锁对象上来说,
  • synchronized 锁的是对象,锁信息保存在对象头中
  • ReentrantLock锁的是线程,锁的信息保存在AQS 的state标识,以及ownerThread字段。
  1. 使用上来说
  • synchronized 可以用来修饰方法,代码块。由Jvm自动释放锁
  • ReentrantLock只能修饰代码块。需要手动释放锁
  1. 功能上来说 ReentrantLock 相对 synchronized 多了三中功能:1)等待可中断,2)公平锁,3)可绑定多个条件,实现有选择的唤醒等待
  1. ReentrantLock 的关键代码
//ReentrantLock中有一个抽象类Sync,它继承了AbstractQueuedSynchronizer
public class ReentrantLock implements Lock, java.io.Serializable {

        private final Sync sync;
        abstract static class Sync extends AbstractQueuedSynchronizer {
                ......
        }
}

// 根据构造方法的Boolean参数,实例化Sync的不同实现类
//FairSync, NonfairSync

//AbstractOwnableSynchronizer,有一个volatile修饰的state,以及
//AbstractOwnableSynchronizer类中的字段exclusiveOwnerThread,还有一个Node类,作为等待队列,封装了等待线程
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

    private volatile int state;
    private transient Thread exclusiveOwnerThread; //AbstractOwnableSynchronizer类中的字段
    private transient volatile Node head;
    private transient volatile Node tail;
    
    /** CLH Nodes */
    abstract static class Node {
        volatile Node prev;       // initially attached via casTail
        volatile Node next;       // visibly nonnull when signallable
        Thread waiter;            // visibly nonnull when enqueued
        volatile int status;      // written by owner, atomic bit ops by others
        .......
    }  


  1. ReentrantLock 的等待可中断是怎么实现的

使用 lock.lockInterruptibly(); 加锁。其会响应 threadB.interrupt();

abstract static class Sync extends AbstractQueuedSynchronizer {

        .........
        final void lockInterruptibly() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            if (!initialTryLock())
                acquireInterruptibly(1);
        }      
}
  1. ReentrantLock 的绑定多个条件是怎么实现的
    1. ReentrantLock
    public Condition newCondition() {
        return sync.newCondition();
    }
    2.Sync
    final ConditionObject newCondition() {
            return new ConditionObject();
    }
    3.AQS
     public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        /** First node of condition queue. */
        private transient ConditionNode firstWaiter;
        /** Last node of condition queue. */
        private transient ConditionNode lastWaiter;
        
        //将当前线程封装为一个conditionObject,入队阻塞
        public final void await() throws InterruptedException {
        ....
        }
        //将条件队列中的首节点,加入到同步(等待)队列中
        public final void signal() {
        .....    
        }


     }
     static final class ConditionNode extends Node
        implements ForkJoinPool.ManagedBlocker {
        ConditionNode nextWaiter;            // link to next waiting node
         */
        public final boolean isReleasable() {
            return status <= 1 || Thread.currentThread().isInterrupted();
        }
        public final boolean block() {
            while (!isReleasable()) LockSupport.park();
            return true;
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值