Java ReentrantLock

1. 锁的意义——线程同步

2. 案例——基于ReentrantLock来实现同步

public class A {

    public static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) {
        new Thread(() -> {
            testSync();
        }, "t1").start();

        new Thread(() -> {
            testSync();
        }, "t2").start();


    }

    public static void testSync(){

        reentrantLock.lock();
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            reentrantLock.unlock();
        }
    }
}

3. ReentrantLock跟Synchronized的区别?

ReentrantLock 1.6之后才出现 

1. 其中while + CAS就是自旋 —— 同时会空转,耗费cpu

2. 针对第一点可以尝试添加yield ,主动让出cpu,但是具体还是内部调度器自身实现的

3. 针对第一点可以尝试添加sleep,但是主要问题在于没法确定休眠时间,过多过少都有问题

4. 使用park+自旋【park是由unsafe提供的,unsafe可以申请堆外内存, park方法可以让当前线程立即睡眠】【其中需要注意:park也要调用osrere】

public class A {

    public static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            testSync();
        }, "t1");
        t1.start();


        Thread.sleep(1000);
        System.out.println("main-1");
        LockSupport.unpark(t1);
    }

    public static void testSync(){

        System.out.println("t1-1");
        LockSupport.park();
        System.out.println("t1-2");
    }
}

4. ReentrantLock理解?

1. 有两种场景,公平锁和非公平锁。属于哪一种取决于具体参数

 

2. 调用lock

 

3. acquire 【注意:tryAcquire是取非】

4. tryAcquire

——获取当前线程

——getState 获取一个volatile 类型State的值,默认是0

——c == 0,本质上判断的是锁是否是自由状态

 ——接续看:在一个线程占有锁的情况下,其他线程来了,那么会进入队列,如:t1占有锁,t2、3、4在队列里面;

 ——t1执行完毕,那么需要:释放锁(state=0,unpark,维护队列XXX)

 ——那么问题来了,假如state=0的时候,t5来了,能直接cas吗?对t2、3、4公平吗?

 ——如果直接cas,那么这种情况下就叫做非公平锁

 ——所以:公平与非公平就只有这一个区别(!hasQueuedPredecessors —— 判断自己是否需要排队)

5. AQS

Node里面有当前线程ID、有pre、next 

AbstractQueuedSynchronizer里面有head、tail

6. hasQueuePredecessors

——在刚开始的时候,h、t都是null。所以直接返回false

7. 基础属性

private transient volatile Node head;
private transient Thread exclusiveOwnerThread;   //当前持有锁的线程
private transient volatile Node tail;
private volatile int state;                      //锁的状态

 所以,这里返回为true,回到Acquire。整体结束 

所以说:同步就是为了让lock方法正常返回    reentrantLock.lock(); 

 多线程t1没有执行完的情况下【对于t1而言:获取到锁的自由状态,然后判断是否需要排队】,t2来了【也就是修改锁的计数器】

也就是tryAcquire返回false。           

 

 

因为刚开始初始化的时候tail为null。所以只想enq 【初始化】

务必关注:AQS队列里面的队头结点的thread永远为null

注意:所谓的入队就是维护好链表关系

 

来一个真实场景【t1获取锁,t2抢锁失败,进入队列,进入后需要自旋,因为t1有可能结束】

首先:队列里面第一个不是排队的,第二个才是排队的【如:去火车站买票,第一个人是排队吗?肯定不是啊,正在受理业务,只有第二个人才叫排队】  

只有第二个才有自旋的资格,相当于运行过程中,定期询问第一个是否办理完成

但是后续的,如:第三个就没有资格询问,因为前面的人都没有弄完 【当然:这里原则不变,也就是AQS里面,队头的thread永远为null,说明第二个人永远是第一个排队的 】

重点来说:如果前一个是头部的话,那么当前就是第二个结点,会进行tryAcqquire,也就是自旋一次 。

tryAcquire 如果获取不到锁,那么就返回false

volatile int waitStatus;  //这个是Node的状态

假如加锁失败:那么进入到shouldParkAfterFailedAcquire。调整当前结点状态,将ws置为-1,同时返回false

因为返回false。所以acquireQueued里面重新自旋,假如还没有获取到锁shouldParkAfterFailedAcquire 会返回true

这里会park当前线程

所以:这里总结下,总共需要自旋两次【t2】。这里也牵扯到Node.SIGNAL 0和-1的变化,为什么需要这一次呢?因为尽量在提升效率的时候,避免park

但是:t3不进行自旋,因为没有资格啊

根据shouldParkAfterFailedAcquire,也就是后一个线程会把前一个线程Node状态修改为-1 

-1表示在进行休眠

为什么需要下一个结点来进行调整呢?

因为当前线程休眠后没法修改状态。或者修改状态后,不一定会正常进入休眠

类比:我自己睡着了,这个是别人看到的

因为这个时候,队列还没有初始化

 

 

 

 

 

 

 

5. 核心

关于AQS或者ReentrantLock

1. 自旋

2. CAS

3. park-unpark

同时:单线程或者多线程交替执行其实和队列无关 —— jdk级别解决并发问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值