多线程笔记6——JUC并发包中锁原理

1.LockSupport工具类

主要作用是挂起和唤醒线程,底层是由Unsafe类实现。

void park()/
void unpark(Thread thread /
void parkNanos(long nanos)/
park(Object blocker)Thread 类里面有个变量 volatile Object parkBlocker 用来存放 park 方法传递的 block 对象,也就是把blocker 变量存放到了调用 park 方法的线程的成员变量里面。

2.AbstractQueuedSynchronizer(AQS)

全称AbstractQueuedSynchronizer,是阻塞式锁和相关同步器工具的框架,并发包中锁的底层就是使用AQS实现的。

2.1 成员变量

内部是一个FIFO的双向队列

重要的属性包括:state;Node head指向第一个被挂起的线程; Node tail指向随后一个被挂起的线程

AQS是个抽象类(需要被继承实现),中间有ConditionObject和Node两个静态内部类

1.state:表示资源的状态:

ReentrantLock:当前线程获得锁的可重入次数(=0表示可获得锁,>0表示锁已被占用,需要判断是否是自身锁)

ReentrantReadWriteLock:state高16位表示读状态(获取读锁的次数),低16位表示获取写锁的线程可重入次数

semaphore:state表示当前可用信号的个数

CountDownlatch:state表示计数器当前的值

state方法:1.getState:获取state状态;2. setState:设置state状态;3. compareAndSetState:CAS设置state状态

2.2 内部类Node

Node记录每个线程:

SHARED:用来标记该线程是获取共享资源时被阻挂起后放入AQS 队列的

EXCLUSIVE:用来标记线程是获取独占资源时被挂起后放入 AQS 队列的

waitStatus:记录当前线程等待状态,可以为 CANCELLED (线程被取消了),SIGNAL(线程需要被唤醒),CONDITION(线程在条件队列里面等待),PROPAGATE(释放共享资源时需要通知其他节点)

prev 记录当前节点的前驱节点,next 记录当前节点的后继节点

2.3 内部类ConditionObject

每个ConditionObject是一个单项链表队列(条件队列),其用来存放调用条件变量的await 方法后被阻塞的线程

这个条件队列的头、尾元素分别为 firstWaiter 和 lastWaiter。

 

2.4 介绍

本身是一个抽象类,主要通过继承的方式使用,分为独占模式和共享模式:

独占模式:只有一个线程可以访问资源;如ReentrantLock;

独占模式方法有:void acquire( int arg);void acquirelnterruptibly(int arg);boolean release(int arg);

共享模式:多个线程可以访问资源;如ReantrantReadWriteLock

共享模式方法有:void acquireShared(int arg);void acquireSharedinterruptibly(int arg);boolean releaseShared(int arg);

2.5 使用方法与原理

2.6 中实现了自定义锁,其中MySync继承了AQS,在加锁时只需在lock方法中调用mysync.acquire()和mysync.release()方法。

这些方法AQS都已经提供无需重写。需要在MySync中重写的是tryAcquire()和tryRelease()方法。

原理如下:acquire()和release()中调用了tryAcquire()和tryRelease()

子类需要重写的方法:

1.tryAcquire() / tryAcquireShared():尝试加 独占 / 共享 锁

2. tryRelease() / tryReleaseShared():尝试解锁 独占 / 共享 锁

3. isHeldExclusively:判断锁是被独占还是共享

4.newCondition();new一个ConditionObject对象

2.6 自定义锁实例

//自定义锁,不可重入锁
class MyLock implements Lock{
    
    //独占锁
    class Mysync entends AbstractQueuedSynchronizer {
        @Override    //尝试获取锁
        protected boolean tryAcquire(int arg){
            if(compareAndSetState(0, 1)){
                //加锁成功
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        
        @Override    //尝试释放锁
        protected boolean tryRelease(int arg){
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override    //是否持有独占锁,该方法重写AOS,AOS是AQS父类
        protected boolean isHeldExclusively(){
            return getState() == 1;
        }
        
        public Condition newCondition(){
            return new ConditionObject();
        }
        
    }

    private Mysync sync = new Mysync();

    @Override    //加锁(加锁失败后会进入等待队列等待)
    public void lock(){
        sync.acquire(1);
    }

    @Override    //加锁,可打断
    public void lockInterruptibly() throws InterruptedException{
        sync.lockInterruptibly(1);
    }

    @Override    //尝试加锁(加锁失败只会返回false,)
    public boolean tryLock(){
        sync.tryAcquire(1);
    }

    @Override    //尝试加锁带超时(加锁失败只会返回false)
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException{
        sync.tryAcquireNanos(1, unit.toNanos(time))
    }

    @Override    //解锁
    public void unlock(){
        sync.release(1);
    }

    @Override    //创建条件变量
    public Condition newCondition(){
        return sync.newCondition();
    }
}

3.ReentrantLock原理

ReentrantLock内部的Sync锁同步器,继承了AQS,分为公平锁和非公平锁

1.非公平锁实现原理

构造器默认为非公平锁

//NonefairSync继承了AQS
public ReentrantLock(){
    sync = new NonfairSync();
}

2.可重入锁原理

判断state==0时:表示锁可以被占有

state!=0时:如果当前线程==占有锁的线程:state+1;锁重入成功

                    如果当前线程!=占有锁的线程:枷锁失败

if(state为0){
    CAS加锁成功
    return true;
}
else{    //state不为0
    
    if(当前线程=Owner中的线程){
        state += 1;锁重入  
        return true;
    }
}

4. ReentrantReadWriteLock读写锁

读读可以并发;读读或者写写互斥

注意:

读锁不支持条件变量

重入不支持升级:即持有读锁的情况下再去尝试获得写锁,会导致永久等待

重入支持降级:持有写锁的情况下尝试获取读锁

class DataContainer{

    private Object data;
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock r = rw.readLock();
    private ReentrantReadWriteLock.WriteLock w = rw.writeLock();

    public Object read(){
        r.lock();
        try{
            return data;
        }finally{
            r.unlock();
        }    
    }

    public Object write(){
        w.lock();
        try{
            //修改data;
            return data;
        }finally{
            w.unlock();
        }    
    }

}

5.StampedLock

该类从JDK1.8加入,是为了进一步优化读性能。特点是在使用读锁,写锁时都必须配合戳使用。

ReentrantReadWriteLock在进行读读操作时底层还是使用AQS,CAS修改状态

public class Test{
    private int data;

    //创建StampedLock
    private final StampedLock lock = new StampedLock();
    
    public int read(){

        //尝试乐观读
        long stamp = lock.tryOptimisticRead();
        //修改data
        
        //如果校验成功(stamp戳没变表示没有其他线程干扰过)——>成功
        if(lock.validate(stamp)){
            return data;
        }
        try{
            //如果失败就执行readLock锁升级,和ReentrantReadWriteLock一样
            stamp = lock.readLock();
            //修改data
            return data;
        }finally{
            //解锁
            lock.unlockRead(stamp);
        }
    }

}

缺点:不支持条件变量;不支持可重入

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值