多线程——ReentrantLock

一、初识 ReentrantLock

ReentrantLock 这个是大佬 Doug Lea 写的,在早期的 java 版本中,synchronized 效率低,原因是线程的阻塞和唤醒,操作系统需要在用户态和内核态切换,线程阻塞和唤醒的代价比较高。然后大佬就看不下去了,自己写(不仅是ReentrantLock哈)。当然咯,synchronized 毕竟是亲儿子,随着时间的推移,不断对其进行优化,性能方面 synchronized 已经有了很大提升了。
首先 ReentrantLock 在 java 中是一个基础的锁对象, ReentrantLock 实现了 Lock 接口提供的一系列的基础函数,所以相比传统的 synchronized 而言,功能上更丰富,使用起来更为灵活,也更适合复杂的并发场景。在这之前先来一段代码,看看它是怎么使用的:

public class ReentrantLockTest {
	//javap -p -v 对应类的.class 文件路径
	private static final Lock lock = new ReentrantLock(true);
	private static final Object obj = new Object();
	public static void main(String[] args) {
		new Thread(()->test(),"AAA").start();
		new Thread(()->test(),"BBB").start();
		new Thread(()->test(),"CCC").start();
		new Thread(()->test(),"DDD").start();
	}
	public static void test() {
		for(int i = 0 ; i < 20;i++) {
			try{
//				synchronized (obj) {
//					System.out.println(Thread.currentThread().getName()+" get");
//					Thread.currentThread().sleep(200);
//				}
				lock.lock();
				System.out.println(Thread.currentThread().getName()+" get");
				Thread.currentThread().sleep(150);
		
			}catch(Exception e) {
				e.printStackTrace();
			}finally {
				lock.unlock();
			}	
		}
	}
}

javap -p -v 对应类的.class 文件路径 可以查看.class 文件里面的内容,这段代码是以前在别人文章中看到,刚刚突然发现了,就拿出来了,这段代码 使用 synchronized 和 lock 控制台输出的内容顺序是不一致的,有兴趣的小伙伴可以在自己的电脑上试试,不过重点不在这里,这里只是简单的看一下 ReentrantLock 怎么使用。

二、源码和API

Ⅰ、类的定义:

public class ReentrantLock implements Lock, java.io.Serializable

Ⅱ、包含的内部类

abstract static class Sync extends AbstractQueuedSynchronizer
static final class NonfairSync extends Sync
static final class FairSync extends Sync

Ⅲ、Sync 类

Sync 是一个抽象类,这个类继承了 AQS,并提供了 lock 抽象方法,源码中对该类的解释为:

    /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    /**
     * 此锁的同步控制基础。下面细分为公平和非公平版本。使用 AQS 状态标识锁的保留数
     */
       /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
         /**
          * 执行不公平的 tryLock。tryAcquire 是在子类中实现的,但是都需要对 trylock 
          * 方法进行不公平的尝试
          */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread(); // 获取当前线程
            int c = getState();  // 获取状态值
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {  // CAS 设置状态值 
                    setExclusiveOwnerThread(current);  // 将当前线程设置为 独占模式的当前所有者
                    return true;  // 设置成功 返回 true
                }
            }
            else if (current == getExclusiveOwnerThread()) {  // c 不为0,可能是重入这一种情况,所以获取它的所有者线程,并判断是否是当前线程对象
                int nextc = c + acquires;  // 是当前线程对象,更新后的状态值
                if (nextc < 0) // overflow 如果更新后的值小于 0,说明重入的次数太多,导致溢出了
                    throw new Error("Maximum lock count exceeded");  // 抛出 Error
                setState(nextc); //设置状态
                return true;  //返回 true
            }
            return false;
        }
		protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            // 虽然我们必须在拥有者之前先以一般状态读取状态,但我们不需要这样做就可以检查
            // 当前线程是否为拥有者
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

tryRelease、newCondition、getOwner、getHoldCount、isLocked 没什么好分析的,这里就啰嗦了

Ⅳ、NonfairSync 类

同步对象的非公平锁,继承 Sync 类,并对 lock 方法进行了具体的实现


        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        // 执行锁定 尝试立即执行 barge,失败后恢复到 acquire 
        final void lock() {
            if (compareAndSetState(0, 1))  // CAS 将状态值设置为 1 
                setExclusiveOwnerThread(Thread.currentThread()); // 将当前线程设置为 独占模式的当前所有者
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

Ⅴ、FairSync 类

同步对象的公平锁,继承 Sync 类,并对 lock 方法进行过了具体的实现

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

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
         // 公平版本的 tryAcquire.除非递归调用或没有 waiters,否则
         // 不需要授予访问权限
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread(); //获取当前线程
            int c = getState();   // 获取状态值
            if (c == 0) {
                // 如果当前线程位于队列的开头第一个线程或队列为空 并且 CAS 设置状态成功,则进入if语句块中
                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;
        }

看完上面的代码后,我们便可以分析 公平锁 与 非公平锁的实现(其实就是上面的源码)

非公平锁

非公平锁的实现: 看上面 NonfairSync 类 的代码,它 lock 方法中,if 条件判断是 CAS 设置状态是否可以设置成功,
换句话说,如果此时多个线程同时设置(一次只能一个线程设置成功),那么具体哪个线程会执行成功,取决于 CPU,因为多个线程都在抢同一个资源(state),CPU 执行哪个成功,哪个就设置成功,而这个是随机无法确定的,所以这便是 非公平锁的实现。

公平锁

公平锁的实现: 看上面 FairSync 类的代码,它的 lock 方法中调用了 “acquire(1);” 方法,它的代码如下面所示,acquire方法中又调用了 tryAcquire 方法,这个方法是子类重写的方法,所以此时又会回到 子类实现中,此时便有三种情况了,第一种,获取的 state 为 0;第二种情况, state 不为 0,但独占线程和当前线程相等(即重入情况);第三,其它情况;
第一种情况:同步队列为空,这种情况 CAS 对 state 进行设置,设置成功后,并将当前线程设置为独占
第二种情况:重入
第三中情况: 在 tryAcquire 返回 false. 则 在调用 tryAcquire 的 acquire 方法中,第一个条件false 取反后为 true,执行第二个,尝试入队,
入队成功后,调用 selfInterrupt 方法,源码如下所示,中断调用该方法的 Thread 实例所代表的线程。

  public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
 static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

三、总结

小结
ReentrantLock 是一个可重入的锁,具有与使用 synchronized 方法和语句访问的隐式监视锁仙童的基本行为和语义,但它具有扩展功能
构造函数设置伪 true,在争用下,锁有利于访问最长等待的线程(可将上面代码的true改为false 运行,对比查看控制台结果)
此类的序列化与内置锁的操作方式相同:无论其序列化时的状态如何,反序列化锁都将处于未锁定状态
非公平锁是通过抢占的方式来保证的
公平锁是通过同步队列来保证的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值