当前位置:主页 > 查看内容

多线程系列:ReentrantLock的小秘密

发布时间:2021-06-10 00:00| 位朋友查看

简介:ReentrantLock基础 ReentrantLock入门 基础使用 公平锁FairSync与非公平锁NonfairSync 公共代码解读 NonfairSync 核心代码解读 FairSync核心代码解读 LocksSuport LocksSuport与Object的wait、notify区别 AQS 个人理解 AQS特性 常见应用 常见问题 线程的 run……

在这里插入图片描述

ReentrantLock入门

ReentrantLock是一种基于AQS框架的实现,也是JDK1.5后的一种线程并发访问的同步手段,它类似于synchronized是一种互斥锁,可以保证线程安全。它比synchronized更多的特性,比如它支持加锁的公平性、支持手动加锁与解锁。

基础使用

import java.util.concurrent.locks.ReentrantLock;
/**
 * @program: 多线程测试
 * @description:
 * @author: cc百川 https://blog.csdn.net/ljcc122/
 * @create: 2021-04-10 13:52
 **/
public class Test1_UserLock {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock rlock = new ReentrantLock();//*1* 默认申请非公平锁[^1] NonfairSync
        for(int i=0;i<10;i++) {
            new Thread(()->{
                doRun(rlock);
            }, i+"").start();
        }
        Thread.sleep(1000);
        System.out.println(count);
    }
    /**
     * 线程run方法内部逻辑
     * @param rlock
     */
    public static void doRun(ReentrantLock rlock){
        rlock.lock();//*2*锁竞争标识
        //获得锁后进入下面的逻辑,没有获取锁的进入自旋等待当前锁释放*3*
        try{
            for(int i=0;i<100;i++)
                count++;
        } finally {
            rlock.unlock();//用完后释放锁,这也就是与synchronized的区别之一需要显示的加释锁
        }
    }
}

公平锁FairSync与非公平锁NonfairSync

公平锁简单理解就是不区分身份,每一个任务都需要排队。比如:高考进场,不区分你是班长还是课代表,都需要排队进场。
非公平锁简单理解就是,任务由高低可以插队。比如:银行VIP机制,VIP不管后面有没有排队都插队。
在这里插入图片描述

公共代码解读

//加锁核心
public final void acquire(int arg) {
//尝试获取锁,获取失败则自旋进入同步队列,并阻塞自己 LockSupport.park(this);
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//AQS核心同步等待CLH队列(双向链表) 中已Node的方式存储
        selfInterrupt();
}
//入队
final boolean acquireQueued(final Node node, int arg) {
     boolean failed = true;
     try {
         boolean interrupted = false;
         for (;;) {
             final Node p = node.predecessor();
             if (p == head && tryAcquire(arg)) {
                 setHead(node);
                 p.next = null; // help GC
                 failed = false;
                 return interrupted;
             }
             if (shouldParkAfterFailedAcquire(p, node) //设置当前节点的前节点的waitStatus=-1 用于标记是否能唤醒当前节点
             && parkAndCheckInterrupt())//阻塞
                 interrupted = true;
         }
     } finally {
         if (failed)
             cancelAcquire(node);
     }
 }
//释放核心
//释放锁
public void unlock() {
    sync.release(1);
}
//尝试释放资源
public final boolean release(int arg) {
  if (tryRelease(arg)) {//尝试释放资源就是设置state=0,并通知CLH队列,可以进行锁竞争了
        Node h = head;
        if (h != null && h.waitStatus != 0)//这里或判断队列的第一个节点waitStatus 状态是否不等于0,不等于0说明可以唤醒队列中的头节点的下一个节点
            unparkSuccessor(h);//拿到头节点下一个节点的线程,重置下一个节点的数据含设置下一个节点waitStatus=0,头节点后移自旋处会修改为-1
        return true;
    }
    return false;
}

NonfairSync 核心代码解读

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    /**
    设置加锁步骤:
    第一步:直接尝试cas设置锁抢占。如果抢占成功就执行当前任务。
     */
    final void lock() {
        if (compareAndSetState(0, 1))//直接参数锁的抢占
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);//尝试获取资源,成功则返回true,失败则返回false
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

final boolean nonfairTryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
   int c = getState();//AQS状态标记 等于0表示当前没有执行的任务,可以继续执行任务
   if (c == 0) {//等于0标识可以进行锁的争夺
       if (compareAndSetState(0, acquires)) {//通过CAS争夺锁,争夺成功执行自己的线程任务
           setExclusiveOwnerThread(current);
           return true;
       }
   }
   else if (current == getExclusiveOwnerThread()) {//可重入标志,如果当前线程是正在执行任务的线程,再次加锁不需要争夺锁资源,给AQS的状态为+acquires【这里为1】即可,这里也不用考虑线程安全问题,能执行任务的只有一个线程。
       int nextc = c + acquires;
       if (nextc < 0) // overflow
           throw new Error("Maximum lock count exceeded");
       setState(nextc);
       return true;
   }
   return false;
}

FairSync核心代码解读

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    final void lock() {
        acquire(1);//尝试获取资源,成功则返回true,失败则返回false
    }
    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;
    }
}
//判断当前队列是否为空或者当前线程是队列第一个
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());
}

LocksSuport

指定线程的阻塞与唤醒。
内部使用Unsafe的park阻塞、唤醒线程。

public class Test2_LockSuport {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            LockSupport.park();//阻塞当前线程
            System.out.println(Thread.currentThread().getName());
        }, "cc百川lockA");
        Thread t2 = new Thread(()->{
            LockSupport.park();//阻塞当前线程
            System.out.println(Thread.currentThread().getName());
        }, "cc百川lockB");
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("主线程唤醒");
        LockSupport.unpark(t1);//主线程唤醒t1线程
    }
}

结果在这里插入图片描述

LocksSuport与Object的wait、notify区别

  1. 共同点
    LockSupport中的park方法和Object中的wait方法都可以使线程进入WAIT或者TIMED_WAIT状态
    LockSupport中的unpark方法和Object中的notify可以使线程脱离WAIT、TIMED_WAIT状态

  2. 不同点
    Object中的wait方法在调用时当前线程必须要对该Object进行加锁
    Object中wait和notify方法必须要按顺序调用。
    Object中notify不能指定唤醒哪一个线程,之鞥呢通过notifyAll唤醒所有线程。而LockSupport.ubpark(Thread)可以唤醒指定线程。

notify()随机唤醒一个线程,notifyAll()会唤醒所有的线程
唤醒的线程由等待池进入线程锁池中,参与 锁的竞争,竞争成功则执行,如不成功则停 留在锁池中等待锁被释放后,再参与竞争

注意:wait与Thread.sleep区别参考:多线程系列:Java创建线程的方式

AQS 个人理解

AQS(AbstractQueuedSynchronizer)主要利用 CAS (Compare AndSwap)和volatile和native方法来保证原子操作, 从而避免synchronized的高开销,执行效率大为提升,基于FIFO的CLH队列保证了同步阻塞。Unsafe请参考:多线程系列:阻塞队列BlockingQueue分析

AQS特性

重要volatile变量 state记录当前是否存在线程在执行任务,(state>0)
1.阻塞等待队列
2.独占、共享
3.公平与非公平
4.允许中断
5.可重入

常见应用

  • ReentrantLock:是一个独占模式的线程安全锁。
  • ReentrantReadWriteLock:读锁共享锁S,写锁排他锁X
  • Semaphore:通过共享的方式。主要是通过记录信号量的方式,等待有信号量才能执行
  • CountDownLatch:通过共享的方式。通过计数的方式,等待其他线程完成后在继续执行。当计数器为0时表示任务执行完毕,恢复阻塞的线程

常见问题

线程的 run() 和 start() 有什么区别?

start用于启动线程,可以进入线程的生命周期,start只能调用一次。
run方法用于指向线程的运行时代码,run可以重复调用(类似普通方法)。

yiled方法说明

让当前正在运行的线程回到可运行状态,允许相同优先级的其他线程,参与锁竞争运行的机会。

synchronize的原理

synchronized是由一对monitorenter/monitorexit指令实现的, monitor对象是同步的基本实现单
在这里插入图片描述

synchronize的膨胀过程

主要是说的JDK1.6之后
无锁->偏向锁->轻量级锁->重量级锁【注意锁的膨胀不可逆】即升级后不能退回到上一个状态

锁类型原因
偏向锁无实际竞争,且自由一个线程获得锁的全部使用
轻量级锁无实际竞争,多个线程交替使用锁,运行短时间锁的竞争
重量级锁有实际竞争,且锁竞争时间较长

synchronize粗化、消除

需要开启JVM逃逸分析【其他作用标量替换,把对象分配到栈中,减少GC次数】

类型说明图解
粗化指把同一个对象的多个锁粗化为一个锁,在同一个方法中多次对同一个对象加锁,理论上是无意义的,其他线程获取了当前锁在这里插入图片描述
消除指把无用锁消除,这里在方法类加锁且对象不存在竞争即可以不加在这里插入图片描述

持续完善中,望各位看官指导

;原文链接:https://blog.csdn.net/ljcc122/article/details/115572739
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文

  • 周排行
  • 月排行
  • 总排行

随机推荐