java并发编程(终篇)

举报
赵KK日常技术记录 发表于 2023/06/24 12:54:09 2023/06/24
【摘要】 根据大纲整理资料。其实这个完结不了的,因为这个追踪源码debug太耗时间了,都是根据往期资料整理一下,怕时间久了就不太系统,另外最近要看的资料太多,所以尽快整理完吧,要想特别细的整理,或者需要更深层次的理解,还是要下功夫的,美团的技术博客对并发及应用都做了整理,追踪源码和整理的图文都很完美。我根据网易云课堂的大纲整理下,加深下印象,因为笔记记得太散,整理起来也比较麻烦。33:Reentran...

请在此添加图片描述

根据大纲整理资料。

其实这个完结不了的,因为这个追踪源码debug太耗时间了,都是根据往期资料整理一下,怕时间久了就不太系统,另外最近要看的资料太多,所以尽快整理完吧,要想特别细的整理,或者需要更深层次的理解,还是要下功夫的,美团的技术博客对并发及应用都做了整理,追踪源码和整理的图文都很完美。我根据网易云课堂的大纲整理下,加深下印象,因为笔记记得太散,整理起来也比较麻烦。

33:ReentrantLock/ReentrantReadWriteLock

相似:都是可进入的,都是互斥的,都是锁,synchronized实际也是可重入的只不过是jvm层次的,ReentrantLock是代码层次的,重入体现在acquire调了N次

写锁:互斥,

读锁:并行,数据的可见性

ReentrantReadWriteLock实现了ReadWriteLock,核心方法获取读锁与写锁,而ReadLock是一个悲观锁和WriteLock的lock方法实现也是AQS

protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
public void lock() {
            sync.acquire(1);
        }
区别在于acquireShared是共享
public void lock() {
            sync.acquireShared(1);
        }
If the write lock is held by another thread then
* the current thread becomes disabled for thread scheduling
* purposes and lies dormant until the read lock has been acquired.

如果写锁由另一个线程持有,则当前线程将禁用线程调度,在获取读锁之前处于休眠状态。

34:StampedLock and LockSupport

StampedLock 看下源码翻译

 A capability-based lock with three modes for controlling read/write
 * access.  The state of a StampedLock consists of a version and mode.
 * Lock acquisition methods return a stamp that represents and
 * controls access with respect to a lock state; "try" versions of
 * these methods may instead return the special value zero to
 * represent failure to acquire access. Lock release and conversion
 * methods require stamps as arguments, and fail if they do not match
 * the state of the lock. The three modes are:
 1.Writing
 2.Reading
 3.Optimistic Reading

大概意思就是控制读写的访问,返回一个锁访问的状态来确定是否访问,状态不匹配则失败,包含三种模式,读中,写中,乐观读

看下廖雪峰老师的资料

https://www.liaoxuefeng.com/wiki/1252599548343744/1309138673991714

将StampedLock 定义为新的读写锁,解决ReadWriteLock读的过程中不允许写的问题

StampedLockReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入!这样一来,我们读的数据就可能不一致,所以,需要一点额外的代码来判断读的过程中是否有写入,这种读锁是一种乐观锁。

看下源码中的构造方法

tryOptimisticRead

 /**返回版本号
     * Returns a stamp that can later be validated, or zero
     * if exclusively locked.
     *
     * @return a stamp, or zero if exclusively locked
     */
    public long tryOptimisticRead() {
        long s;
        return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
    }

将lock和乐观锁的思想结合,需要手动释放锁,通过验证版本号判断是否有其他写锁发生。

LockSupport核心方法park() unpark()用于唤醒/阻塞线程

public static void park() {
        UNSAFE.park(false, 0L);
    }
public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

不管是park还是unpark都是通过unsafe类来调用实现的。

35:悲观锁和乐观锁

悲观锁(Pessimistic Lock), 每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。

乐观锁(Optimistic Lock), 每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。

两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

36:公平锁和非公平锁

ReentrantLock的子类Sync类的final static子类FairSync和NonFairSync用于支持公平锁和非公平锁。

AQS的tryAcquire()和FairSync的tryAcquire()判定是否为公平锁,其实现也是偏向锁UseBiaseLock的实现

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
 static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        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;
        }
    }

摘抄如下

该方法首先会判断当前线程的状态,如果c==0 说明没有线程正在竞争锁。(反过来,如果c!=0则说明已经有其他线程已经拥有了锁)。如果c==0,则通过CAS将状态设置为acquires(独占锁的acquires为1),后续每次重入该锁都会+1,每次unlock都会-1,当数据为0时则释放锁资源。其中精妙的部分在于:并发访问时,有可能多个线程同时检测到c为0,此时执行compareAndSetState(0, acquires))设置,可以预见,如果当前线程CAS成功,则其他线程都不会再成功,也就默认当前线程获取了锁,直接作为running线程,很显然这个线程并没有进入等待队列。如果c!=0,首先判断获取锁的线程是不是当前线程,如果是当前线程,则表明为锁重入,继续+1,修改state的状态,此时并没有锁竞争,也非CAS,因此这段代码也非常漂亮的实现了偏向锁。

37:自旋锁见CAS内部实现

ps:印象笔记中记载了大部分问题,可以实现免费搜索图片文字,有道云就不行,还得升级会员

38:Random and ThreadLocalRandom

ThreadLocalRandom在juc包里,继承了Random

源码翻译

use of {@code ThreadLocalRandom} rather
 * than shared {@code Random} objects in concurrent programs will
 * typically encounter much less overhead and contention

其超高性能体现在less overhead and contention 通常会遇到更少的开销和争用。它解决了Random类在多线程下多个线程竞争内部唯一的原子性种子变量而导致大量线程自旋重试的不足。

看了下所谓的初始化种子变量都是原子引用

 /** Generates per-thread initialization/probe field */
    private static final AtomicInteger probeGenerator = new AtomicInteger();
         /**
     * The next seed for default constructors.
     */
    private static final AtomicLong seeder = new AtomicLong(initialSeed());

高性能表现方法current()

 static final ThreadLocalRandom instance = new ThreadLocalRandom();
 public static ThreadLocalRandom current() {
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
            localInit();
        return instance;
    }

每次产生随机数不会创建多个实例,产生随机数直接调用

ThreadLocalRandom.current().nextInt(int);

接下来的线程池等其他知识点就总结到这,研究线程池的时候加上,并发篇也就算告一段落了,根据大纲自己整理看下源码,体会下高并发的魅力。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。