并发编程10

并发编程10

读写锁

  • 读读并发

  • package BingFaBianCheng.DuXieSuo;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    @Slf4j(topic = "enjoy")
    public class LockTest10 {
        static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
        static Lock r = rwl.readLock();
        static Lock w = rwl.writeLock();
    
        public static void main(String[] args) throws InterruptedException {
            //读
            new Thread(() -> {
                log.debug("read 获取 锁");
                r.lock();
                try {
                    for (int i = 0; i < 10; i++) {
                        m1(i);
                    }
                } finally {
                    r.unlock();
                }
            }, "t1").start();
            //写
            new Thread(() -> {
                log.debug("write 获取 锁");
                w.lock();
                try {
                    // 读写支持重入但是只支持降级不止升级
                    for (int i = 0; i < 20; i++) {
                        m1(i);
                    }
                } finally {
                    w.unlock();
                }
            }, "t2");
            
            //读
            new Thread(() -> {
                log.debug("write 获取 锁");
                r.lock();
                try {
                    for (int i = 0; i < 20; i++) {
                        m1(i);
                    }
                } finally {
                    r.unlock();
                }
            }, "t3").start();
        }
    
        public static void m1(int i) {
            log.debug("exe" + i);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
  • 情况一,只有t1和t2时,读写互斥,谁先拿到锁,需要等它执行完才能执行另外一个

  • 情况二,只有t1和t3时,读读并发,两者是同步执行的

  • 情况三,肯定是写写互斥

  • 读锁不支持条件,r.newCondition()会直接报异常,写锁支持条件

  • 读写支持重入只支持降级不支持升级(读到写属于升级)

  • package BingFaBianCheng.DuXieSuo;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    @Slf4j(topic = "enjoy")
    public class LockTest11 {
        static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
        static Lock r = rwl.readLock();
        static Lock w = rwl.writeLock();
    
        public static void main(String[] args) throws InterruptedException {
            // 先写后读可执行
            // 先读后写会卡住
            new Thread(() -> {
                log.debug("read");
                w.lock();
                try {
                    log.debug("read 已经获取");
                    r.lock();
                    log.debug("write 已经获取");
                } finally {
                    r.unlock();
                    w.unlock();
                }
            }, "t1").start();
        }
    }
    
  • package BingFaBianCheng.DuXieSuo;
    
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    //缓存数据模式参考
    class CachedData {
        Object data;
        //判断缓存是否过期
        volatile boolean cacheValid;
        // 独占锁
        final ReentrantLock lock = new ReentrantLock();
        //定义一把读写锁(共享锁)
        final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
        //处理缓存的方法
        //查询缓存数据
        void processCachedData() {
            rwl.readLock().lock();
            //如果缓存没有过期则调用 use(data);
            if (!cacheValid) {
                // 缓存过期,需要去load真实数据  set给缓存拿到写锁
                // 释放读锁 因为不能升级 所以需要先释放 
                rwl.readLock().unlock();
    
    
                rwl.writeLock().lock();
                try {
                    //双重检查
                    //怕有别的线程把缓存改为有效了
                    if (!cacheValid) {
                        //从数据库查询到真实数据
                        data = "数据库得到真实数据";
                        cacheValid = true;
                    }
    
                    //更新缓存之后接着读取 所以先加锁
                    rwl.readLock().lock();
                } finally {
                    rwl.writeLock().unlock(); // Unlock write, still hold read
                }
            }
            
            
            try {
                //不管上面的if进不进都会执行这里
                //这里是始终执行的
                //缓存可用 
    		    use(data);
            } finally {
                rwl.readLock().unlock();
            }
        }
    }
    }
    

AQS

定义

1、全称是 AbstractQueuedSynchronizer
2、阻塞式锁和相关的同步器工具的框架,不能获取到锁就会被阻塞;
3、AQS用一个变量(volatile state) 属性来表示锁的状态,AQS自己不去维护这个状态,子类去维护这个状态
3、子类可以通过getState获取状态,以及compareAndSetState cas改变这个变量
4、独占模式是只有一个线程能够访问资源 – ReentrantLock
5、而共享模式可以允许多个线程访问资源(读写锁)-- ReentrantReadWriteLock
6、内部维护了一个FIFO等待队列 --Node head、Node tail(Node{Node pre;Node next;int status}链表实现的双向队列),类似于 synchronized关键字当中的 Monitor 的 EntryList(c++源码)
7、条件变量来实现等待、唤醒机制,支持多个条件变量(LockTest6–Condition cond = lock.newCondition();),类似于 Monitor 的 WaitSet
8、内部维护了一个Thread exclusiveOwnerThread 来记录当前持有锁的那个线程

功能

  • 上面那些定义类似于spring支持AOP、支持声明式事务、支持IOC、支持循环依赖、支持各种注解、能不能基于这些东西,实现一些功能呢?下面就是基于上面那些定义实现的一些功能。

1、实现阻塞获取锁 acquire,拿不到锁就去阻塞,线程睡眠,等待锁被释放再次获取锁
2、实现非阻塞尝试获取锁tryAcquire,拿不到锁则直接放弃,只会拿一次,是非阻塞的
3、实现获取锁超时机制 – tryAcquire(Long timeout, TimeUnit unit)
4、实现通过打断来取消 – lockInterruptibly
5、实现独占锁及共享锁 – ReentrantLock、ReentrantReadWriteLock
6、实现条件不满足的时候等待 – lock.newCondition()

  • // 普通加锁,不可打断;未获取到锁进入AQS阻塞
    void lock();
    
    // 可打断锁
    void lockInterruptibly() throws InterruptedException;
    
    // 尝试加锁,未获取到锁不阻塞,返回标识
    boolean tryLock();
    
    // 带超时时间的尝试加锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
    // 解锁
    void unlock();
    
    // 创建一个条件队列
    Condition newCondition();
    

实现

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KQzfjnHB-1607616788444)(C:\Users\liusiping\AppData\Roaming\Typora\typora-user-images\image-20201208083234813.png)]

  • package BingFaBianCheng.DuXieSuo;
    
    import java.util.concurrent.locks.AbstractQueuedSynchronizer;
    import java.util.concurrent.locks.Condition;
    
    // 同步器,为锁服务
    public class EnjoySync extends AbstractQueuedSynchronizer {
    
        // 里面有一个acquire方法,不需要重写,尝试拿锁,拿不到就阻塞
        // tryAcquire是空的,后面的阻塞是实现了的
        @Override
        protected boolean tryAcquire(int arg) {
            if (getState()==0){
                boolean b = compareAndSetState(0,1);
                if (b){
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
            }
    
            return false;
        }
    
        // 目前没有考虑共享,太复杂了
        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        
        // 没有模拟unPark方法,把线程唤醒,比较复杂
    
        // 有没有人持有
        @Override
        protected boolean isHeldExclusively() {
            boolean f = getState()==0;
            // 等于0
            if (f) {
                return false;
            }else {
                // ==1 持有
                // >1 重入
                return true;
            }
        }
    
        public Condition newCondition(){
            return new ConditionObject();
        }
    
        @Override
        protected int tryAcquireShared(int arg) {
            return super.tryAcquireShared(arg);
        }
    
        @Override
        protected boolean tryReleaseShared(int arg) {
            return super.tryReleaseShared(arg);
        }
    }
    
    
  • package BingFaBianCheng.DuXieSuo;
    
    import BingFaBianCheng.DuXieSuo.CustomSync;
    import BingFaBianCheng.DuXieSuo.LockTest10;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    @Slf4j(topic = "enjoy")
    public class LockTest12 implements Lock {
    
        EnjoySync customSync = new EnjoySync();
    	
        
      	// 加锁--阻塞
        @Override
        public void lock() {
            customSync.acquire(1);
        }
    
        @Override
        public void lockInterruptibly() throws InterruptedException {
            customSync.acquireInterruptibly(1);
        }
    
        @Override
        public boolean tryLock() {
            return customSync.tryAcquire(1);
        }
    
        @Override
        public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
            return customSync.tryAcquireNanos(1, unit.toNanos(time));
        }
    
        // 需要用release方法会唤醒
        // tryRelease方法不会唤醒,只是setExeclusiveThread
        @Override
        public void unlock() {
            // release方法是父类实现的
            customSync.release(1);
        }
    
        @Override
        public Condition newCondition() {
            return customSync.newCondition();
        }
    
        public static void main(String[] args) throws InterruptedException {
            LockTest12 l = new LockTest12();
            // name是t1
            new Thread(() -> {
                // 加锁
                // acquire方法会阻塞,release才会唤醒,tryRealse不会唤醒
                l.lock();
                log.debug("xxx");
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 释放锁
                l.unlock();
            }, "t1").start();
            TimeUnit.SECONDS.sleep(1);
            // 加锁
            l.lock();
            log.debug("main");
            // 释放锁
            l.unlock();
        }
    }
    

ReentrantLock

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gWRIRLVz-1607616788446)(C:\Users\liusiping\AppData\Roaming\Typora\typora-user-images\image-20201209080452061.png)]

非公平锁加锁流程

1.第一个线程t1、第一次加锁,没有加锁之前aqs(NonfairSync)的状态

head:null
tail:null
state:0
exclusiveOwnerThread:null

2.t1加锁之后

head:null
tail:null
state:1
exclusiveOwnerThread:t1

3.第二个线程t2 如果没有释放 aqs状态(state和exclusiveOwnerThread)和2一样

3.1 t2 加锁失败之后(执行addWaiter()方法,addWaiter()方法中又包含一个enq()方法,只修改前置节点,next的数据是错误的)

// 尾节点为null,初始化头节点,一个参数全null的node,head = {thread:null,pre:null,next:null}
// 并且尾结点也等于这个空节点,所以头尾节点都初始化完成
Node head  ->   {thread=null prev=null next=tail waitstatus=-1}
                            ^
                            |
                            V
Node tail  ->    {thread=t2 pre=head next=null waitstatus=0} 
// 死循环继续执行,插入新节点t2Node
// t2Node.prev=t(此处的t也是head节点,因为刚完成初始化);(也就是t2指向Head)
// tail=t2Node(这一步要用cas的写法)(tail指向t2);tail.next=node;(head指向t2)

// 继续执行acquireQueued方法(此时node.predecessor()获取到的是head),一般情况下都会加锁失败

// shouldParkAfterFailedAcquire()方法为false,把head的waitStatus设置为-1
// 等于-1表示我睡醒后()需要去唤醒下一个节点

// &&运算符,前半截为false,所以不会执行parkAndCheckInterrupt()方法
// 由于是死循环for(;;),t2线程会一直去尝试获取锁,进行第二次自旋
// 进行第二次再去加一次锁,一般也是加锁失败,
// 此时执行shouldParkAfterFailedAcquire()方法,waitstatus为-1,返回true

// 执行parkAndCheckInterrupt()方法,整个线程阻塞在这里
// Thread.interrupted()会清除打断标记,t2被打断了也不像Lock.lockInterrupted()方法
// 那样抛出异常,只是清除打断标记,表明自己没有被打断

// 在非公平锁加锁过程中,执行Lock.lockInterrupted()打断线程后,并不会像lockInterrupted方法中那样抛出异常来相应这个打断操作,所以打断了跟没打断看起来是一样的,不会终止这个线程,也不会抛出异常,只是把线程阻塞在了这里,等待他的前置节点来唤醒

// 算上前面的两次加锁,非公平锁被持有后进入的第一个线程一共进行了4次加锁
// cancelAcquire(node)方法执行条件很苛刻,要发生异常才会执行,所以一般不会执行
exclusiveOwnerThread:t1
state:1
状态状态值描述
CANCELLED1取消状态,例如因为等待锁超时而取消的线程;处于这种状态的Node会被踢出队列,被GC回收
SIGNAL-1表示这个Node的继任Node被阻塞了,到时需要通知它
CONDITION-2表示这个Node在条件队列中,因为等待某个条件而被阻塞
PROPAGATE-3使用在共享模式头Node有可能处于这种状态, 表示锁的下一次获取可以无条件传播
其他0初始状态
  • waitStatus是唤醒标识,这个唤醒标识是它的下一个节点替它打上的,这样性能是最优的,同时只有小于0时才会在release方法中执行unPark唤醒被挂起的线程,其余状态不会执行。

3.2 t2加锁成功之后

如果加锁成功则t1必须已经释放了

  • head:null
    tail:null
    state:0
    exclusiveOwnerThread:null
    

然后t2加锁成功

  • head:null
    tail:null
    state:1
    exclusiveOwnerThread:t2
    

结论:ReentrantLock如果线程之间没有竞争效率非常高;队列甚至都没有初始化

4.t3来加锁(t1没有释放)

  • Node head  ->   {thread=null prev=null next=tail waitstatus=-1}
                                ^
                                |
                                V
                    {thread=t2 pre=head next=t3 waitstatus=-1} 
    				//(执行acquireQueue方法后,waitstatus=-1,一直在自旋)
    							^
                                |
                                V
    Node tail  ->   {thread=t3 pre=t2 next=null waitstatus=0}
    				//(执行acquireQueue方法后,waitstatus=0,线程被挂起)
    // 执行addwaiter()方法,没有进入enq()方法
    // t3Node.prev=pred(此处的pred也是t2节点);(也就是t3指向t2)
    // tail=t3Node(此处不要被pred迷惑了,此处的pred是期望值,实际还是更新的tail)
    // (这一步要用cas的写法)(tail指向t3);pred.next=node;(t2指向t3)
    
    // 继续执行acquireQueued方法,由于获取到的(node.predecessor())不是head,不会去尝试获取锁
    // shouldParkAfterFailedAcquire()方法为true
    // 所以继续执行parkAndCheckInterrupt()方法,将t3线程挂起,Thread.interrupted()方法是判断线程是否被成功挂起呢
    // 线程被挂起了,也就停止了,不会继续执行死循环for(;;)
    // t3一共进行了2次加锁
    exclusiveOwnerThread:t1
    state:1
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值