【Java并发编程】ReentrantLock

概述

ReentrantLock是基于AQS构建的,AQS中有一个表示状态的字段state,ReentrantLock用它表示线程重入锁的次数,Semaphore用它表示剩余的许可数量,FutureTask用它表示任务的状态。对state变量值的更新都采用CAS操作保证更新操作的原子性。

类的结构

 ReentrantLock类实现了Lock和java.io.Serializable接口,其内部有一个实现锁功能的关键成员变量Sync类型的sync。而这个Sync是继承了AbstractQueuedSynchronizer的内部抽象类,主要由它负责实现锁的功能。

 private final Sync sync;

 Sync在ReentrantLock中有两种实现类:NonfairSync、FairSync,正好对应了ReentrantLock的非公平锁、公平锁两大类型。

static final class NonfairSync extends Sync
static final class FairSync extends Sync

ReentrantLock的构造函数:

//默认构造函数是非公平锁 
public ReentrantLock() {
        sync = new NonfairSync();
    }


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

获取锁主体流程

 ReentrantLock的锁功能主要是通过继承了AbstractQueuedSynchronizer的内部类Sync来实现的,其lock()获取锁的主要流程如下:

        首先,ReentrantLock的lock()方法会调用其内部成员变量sync的lock()方法;

        其次,sync的非公平锁NonfairSync或公平锁FairSync实现了父类AbstractQueuedSynchronizer的lock()方法,其会调用acquire()方法;

        然后,acquire()方法则在sync父类AbstractQueuedSynchronizer中实现。具体acquire()在上一篇介绍AQS中有。
上述就是公平锁、非公平锁实现获取锁的主要流程,而针对每种锁来说,其实现方式有很大差别,主要就体现在各自实现类的lock()和tryAcquire()方法中。在sync的抽象类Sync及其抽象父类AbstractQueuedSynchronizer中,lock()方法和tryAcquire()方法被定义为抽象方法或者未实现,而是由具体子类去实现:
 

非公平锁实现

 final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

通过代码可以看到,非公平锁上来就无视等待队列的存在而抢占锁,通过基于CAS操作的compareAndSetState(0, 1)方法,试图修改当前锁的状态,这个0表示AbstractQueuedSynchronizer内部的一种状态(AQS中的state字段),针对互斥锁则是尚未有线程持有该锁,而>=1则表示存在线程持有该锁,并重入对应次数,这个上来就CAS的操作也是非公共锁的一种体现,CAS操作成功的话,则将当前线程设置为该锁的唯一拥有者。 抢占不成功的话,则调用父类的acquire()方法,按照上面讲的,继而会调用tryAcquire()方法,这个方法也是由最终实现类NonfairSync实现的。

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) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                //如果拥有锁的就是当前线程,则state+1,重入锁
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

 还是上来先判断锁的状态,通过CAS来抢占,抢占成功,直接返回true,如果锁的持有者线程为当前线程的话,则通过累加状态标识重入次数。抢占不成功,或者锁的本身持有者不是当前线程,则返回false,继而后续通过进入等待队列的方式排队获取锁。

公平锁实现

final void lock() {
            acquire(1);
}


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

当前线程会在得到当前锁状态为0,即没有线程持有该锁,并且通过!hasQueuedPredecessors()判断当前等待队列没有前继线程(也就是说,没有比我优先级更高的线程在请求锁了)获取锁的情况下,通过CAS抢占锁,并设置自己为锁的当前拥有者,当然,如果是重入的话,和非公平锁处理一样,通过累加状态位标记重入次数。
        而一旦等待队列中有等待者,或当前线程抢占锁失败,则它会乖乖的进入等待队列排队等待。

ReentrantLock特点

必须要手动释放锁

ReentrantLock必须要手动释放锁,使用sychronized当遇到异常时,jvm会自动释放锁,但是lock必须要手动释放。

public class ReentrantLock1 {
	Lock lock=new ReentrantLock();
	void m1() {
		//上锁
		lock.lock();
		try {
			for(int i=1;i<10;i++) {
				TimeUnit.SECONDS.sleep(1);
				System.out.println(i);
			}
		}catch(Exception e){
			
		}
		finally {
			//必须要手动释放锁
			lock.unlock();
		}
	}
	void m2() {
		lock.lock();
		System.out.println("m2....");
		lock.unlock();
	}
	public static void main(String[] args) {
		ReentrantLock1 test=new ReentrantLock1();
		new Thread(test::m2).start();
		new Thread(test::m1).start();
	}

}

tryLock

使用 tryLock 可以进行“尝试锁定”,如果无法锁定,或者在指定时间内无法锁定,线程可以决定是否继续等待。

Lock lock=new ReentrantLock();
	void m(){
		 /**
	     * 使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行
	     * 可以根据tryLock的返回值来判定是否锁定
	     * 也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unclock的处理,必须放到finally中
	     */
		boolean locked=false;
		try {
			//尝试获取锁
			locked=lock.tryLock(5, TimeUnit.SECONDS);
			//
			if(locked) {
				System.out.println("lock is true");
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}

指定为公平锁

ReentrantLock lock=new ReentrantLock(true);

中断响应

 lock.lockInterruptibly() 对线程中断 interrupt() 做出响应

使用 lockInterruptibly() 则该线程在等待锁的过程中,如果被中断,则直接抛出中断异常来立即响应中断,由上层调用者处理中断。最后线程结束等锁。

public class ReentrantLock3 {
	Lock lock=new ReentrantLock();
	void m1() {
		lock.lock();
		System.out.println("t1 start");
		try {
			TimeUnit.SECONDS.sleep(10000000);
			System.out.println("t1 end");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
	}
	void m2() {
		try {
			lock.lockInterruptibly();
			System.out.println("t2 start");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			 System.out.println("t2 interrupted!");
		}
	}
	public static void main(String[] args) {
		ReentrantLock3 test=new ReentrantLock3();
		Thread t2=new Thread(test::m2);
		new Thread(test::m1).start();
		t2.start();
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		t2.interrupt();
	}

}


输出:
t1 start
t2 interrupted!

condition

ReentrantLock与synchronized的区别

1. 锁的实现

synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。

2. 性能

新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。

3. 等待可中断

当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。

ReentrantLock 可中断,而 synchronized 不行。

4. 公平锁

公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。

synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。

5. 锁绑定多个条件

一个 ReentrantLock 可以同时绑定多个 Condition 对象。

6.ReentrantLock必须要手动释放锁。

7.ReentrantLock可以尝试获取锁。

使用选择

除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

 

参考:

https://blog.csdn.net/lipeng_bigdata/article/details/52154637

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值