2-JDK源码对你最有触动的是哪一段#工具类

简介: 2-JDK源码对你最有触动的是哪一段#工具类

目录

工具类

WeakReference的使用场景

ServiceLoader

Lock

IntegerCache

位运算

ReentrantReadWriteLock的实现

LongAdder

AbstractQueuedSynchronizer

ThreadLocal基于数组结构的拉链法存储



工具类

WeakReference的使用场景

WeakReference (弱引用) ,用来描述非必须存在的对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。

一直很好奇这个类会在什么时候使用,看这个类的描述,我们在业务开发中好像很少甚至基本没有机会使用这个类,打个比方我们在方法中新建一个对象 A a = new A() 我们不可能写成 WeakReference<A> a = new WeakReference<>(new A()) 这样, 因为这是个弱引用,

万一我方法没执行完成,GC生效了,把我这个对象回收了,接着执行获取到的是个null对象,就出乱子了。

直到有天看到了ThreadLocal中使用了WeakReference,

ThreadLocal<String> threadLocal = new ThreadLocal<String>();
threadLocal.set("1");

先看上面两行代码会发生什么事情 ,例如当前线程名称叫做A,

ThreadLocal.ThreadLocalMap threadLocals = null;


A线程对象内部有一个 名叫threadLocals 的 ThreadLocal.ThreadLocalMap 类型对象。

ThreadLocal.ThreadLocalMap 对象中有一个类型为 ThreadLocal.ThreadLocalMap.Entry 对象数组

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    private Entry[] table;

当我们执行 threadLocal.set("1"); 这行代码的时候, 这个方法会判断当前线程的 ThreadLocalMap 中有没有 key 和当前 threadLocal 对象相等的 Entry ,

如果有则替换Entry对象的value,如果没有,就新建一个key为 threadLocal 对象的 Entry 添加到 线程的ThreadLocalMap 中去,

这里的Entry就是弱引用,为什么这么设置?我们上面说了 一个 Entry 就保存着一个 ThreadLocal 对象,假设Entry是强引用对象,我们现在有这么一段需求,

例如 threadLocal = null; , 我们把这个对象引用置为null,希望下次GC把threadLocal 引用的对象一起回收掉,此刻虽然threadLocal 引用已经被置为空了,但是它之前引用的对象能被回收掉吗?

不可以,因为虽然threadLocal 是 null 了,但是还有ThreadLocalMap 中的 Entry 在引用着这个实际对象,导致GC无法正常回收到,

这显然不是我们想要看到的,如果这个线程一直存在的话,不停的在线程的方法栈中创建使用ThreadLocal threadLocal ,

然后这个变量threadLocal虽然会随着栈帧销毁也没有,但是它指向的对象却因为还有Entry在引用着,一直不回收,所以一直存在堆中,造成了内存泄漏,

所以jdk为了避免这种情况,将Entry继承了WeakReference类,在适当的情况下,让垃圾对象被回收,很优雅的做法。

ThreadLocal.ThreadLocalMap threadLocals 这个属性为什么要放到Thread中呢? 我认为jdk想的是,这样这个对象就可以随线程生,随线程亡了。


ServiceLoader

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

之前在刚使用spring boot时,只是感受到了spring boot的便捷,也听到同事说和SPI有关,但一直不知道SPI是什么,在一次分析JDBC连接异常时,进到JDBC的源码,理解了SPI的思想。

ServiceLoader.load(Driver.class) 通过内部类LazyIterator 将所有数据库驱动jar包下META-INF/services/ 目录中以Driver命名的文件中的实现类进行加载,将加载的驱动信息放到 CopyOnWriteArrayList 中, 在getConnection 时通过传入的URL等信息进行遍历 获取连接;Driver.java 为驱动接口,OracleDriver.java,DruidDriver.java等是它的实现,这种调用方来制定接口规范,提供给外部来实现,调用方在调用时选择自己需要的外部实现的思想,让我对低耦合有了更深刻的认识;同时也对spring boot 中spi的思想有了近一步的认识,通过扩展我还知道了dubbo中也使用了spi的思想,接下来通过这条线看看dubbo中spi的思想运用。


Lock

在我看 JDK 源码的过程中,最有触动的一段是关于 Lock 锁的注释,在 Lock 与 Condition 的接口中,四五百行的文件中基本都是注释,首先讲解了锁的一般概念,然后是与 synchornized 的比较,以及使用的最佳实践。 把 Lock 锁与 synchronized 在使用中的区别将清楚了:

Lock 有更灵活的结构;

Lock 提供了更多特性(其实就是提供的几个方法),比如能够响应中断,支持超时,非阻塞地获取锁;

Lock 支持多个 condition

而且将 Lock 锁的最佳用法写在了注释中:

 Lock l = ...;
 l.lock();
 try {
   // access the resource protected by this lock
 } finally {
   l.unlock();
 }

之后在阿里巴巴的 Java 开发手册中也看到了对 Lock 锁使用的规范,其实就是 JDK 中的注释所写的代码:在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁;以及对这个规范的三个说明。

当初学习 Lock 时看到如此详细的注释,感觉很震感,因为很多概念,用法,甚至最佳实践,可能就存在于JDK 中源码的注释信息中;之后发现 JDK 源码中的注释真的是学习的第一手的资料,但是 Lock 这个真的是我记忆中影响最深的一次了。


IntegerCache

/**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */
private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];
        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
        private IntegerCache() {}
    }

缓存以支持 JLS 要求的 -128 和 127(含)之间值的自动装箱的对象标识语义。 缓存在第一次使用时初始化。 缓存的大小可以由-XX:AutoBoxCacheMax=<size>选项控制。 在VM初始化过程中,java.lang.Integer.IntegerCache.high属性可能会被设置并保存在sun.misc.VM类的私有系统属性中。


【简述】 Jdk1.5中引入了Integer对象的cache机制。最初是因为一道面试题踩了坑,后来仔细看了Integer类才知道这个IntegerCache。这段代码并不难理解,其它的包装类也同时引入了缓存机制,最触动我的地方在于,优秀的框架和源码都在不断地从实践中追求性能和极致,我们应该用怎样的态度对待我们手里的代码呢?


【分析】 按照正常的思维,需要装箱时这里直接new一个对象就好。但这里给定了一个默认范围,[-128,127]内的数字进行装箱时首先会去IntegerCache维护的一个cache[]中获取,没有的话再new一个


这样的好处在于如果有高频的转换,这里的缓存将会节省可观的内存,并加快响应速度。在Integer.valueOf(int i)方法的注释中对此说明:该方法为了更好地能缓存了频繁使用的值(frequently requested values)。


这里引起我兴趣的是这个默认范围的设定[-128,127] 在IntegerCache类的注释中,有写到:Cache to support the object identity semantics of autoboxing for values between -128 and 127 (inclusive) as required by JLS. 这里查阅了资料,最终的结论是: 1.JSL中约定的是-128到127,这是至少要缓存的范围。可以缓存更大的范围,但是[-128,127]是必须的。理想情况下,给定的数值所产生的引用应该总是相同的,但实际上不可能办到。所以设计者给定一个范围,要求至少在[-128,127]是必须要缓存的。 2.这个范围,可以覆盖大多数常见的情况(most common cases),保证相同数值对应相同的引用,而不会施加不当的性能损失,尤其是在小型设备,节约出来的内存将是非常有用的。

【启发】 这段代码其实在当时给了我很大的启发。之前所认为的代码优化,总是会去想一些高大上的技术,但是具体怎么落实并没有思路。但是这段代码带给我的启发是,只要是着力点找到了,用一些简单的手法我们也能有效提升代码的性能,以至于后来的开发工作中,如果某些对象使用很高频,会有意识去维护一个数组,或者集合提升对象的复用,我觉得这也算是看代码带来的一些处理思路上的改进。

记得有道面试题类似下面的,问输出是什么。

public static void main(String[] args) {
Integer a = 1;
Integer b = 1;
Integer c = 128;
Integer d = 128;
System.out.println(a == b);
System.out.println(c == d);
}

没看过源码之前,很想当然的认为两个都是true了。结果匪夷所思为什么c和d就不相等,通过查看源码之后才发现,内部另有玄机。 原因是从JDK1.5之后,Integer 把 -128~127的数字缓存了起来,在自动装箱时调用valueOf(int i) 方法,源码如下,

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

备注

  • 如果==比较的是基本数据类型,那么比较的是两个基本数据类型的值是否相等;
  • 如果==是比较的两个对象,那么比较的是两个对象的引用,也就是两个对象是否为同一个对象,并不是比较的对象的内容;

范围在-128~127之间的数都通过IntegerCache来获取。超出范围的则新建Integer对象。

所以a和b的值都是IntegerCache.cache[129] = 1, 而c 和 d 超出了cache范围,创建了两个值为128的对象,所以对象c和对象d是不相同的的两个对象。

自从第一次开始读起源码,才发现Java代码是如此的优雅,考虑到经常用的东西就把它缓存起来,提高性能。这种思想值得借鉴。 好比现在为什么要用redis等缓存机制,同样的道理是为了提供便捷,提高性能。


位运算

ThreadPoolExecutor 中状态定义和线程数的设置:

/**
     * The main pool control state, ctl, is an atomic integer packing
     * two conceptual fields
     *   workerCount, indicating the effective number of threads
     *   runState,    indicating whether running, shutting down etc
     *
     * In order to pack them into one int, we limit workerCount to
     * (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2
     * billion) otherwise representable. If this is ever an issue in
     * the future, the variable can be changed to be an AtomicLong,
     * and the shift/mask constants below adjusted. But until the need
     * arises, this code is a bit faster and simpler using an int.
     *
     * The workerCount is the number of workers that have been
     * permitted to start and not permitted to stop.  The value may be
     * transiently different from the actual number of live threads,
     * for example when a ThreadFactory fails to create a thread when
     * asked, and when exiting threads are still performing
     * bookkeeping before terminating. The user-visible pool size is
     * reported as the current size of the workers set.
     *
     * The runState provides the main lifecycle control, taking on values:
     *
     *   RUNNING:  Accept new tasks and process queued tasks
     *   SHUTDOWN: Don't accept new tasks, but process queued tasks
     *   STOP:     Don't accept new tasks, don't process queued tasks,
     *             and interrupt in-progress tasks
     *   TIDYING:  All tasks have terminated, workerCount is zero,
     *             the thread transitioning to state TIDYING
     *             will run the terminated() hook method
     *   TERMINATED: terminated() has completed
     *
     * The numerical order among these values matters, to allow
     * ordered comparisons. The runState monotonically increases over
     * time, but need not hit each state. The transitions are:
     *
     * RUNNING -> SHUTDOWN
     *    On invocation of shutdown(), perhaps implicitly in finalize()
     * (RUNNING or SHUTDOWN) -> STOP
     *    On invocation of shutdownNow()
     * SHUTDOWN -> TIDYING
     *    When both queue and pool are empty
     * STOP -> TIDYING
     *    When pool is empty
     * TIDYING -> TERMINATED
     *    When the terminated() hook method has completed
     *
     * Threads waiting in awaitTermination() will return when the
     * state reaches TERMINATED.
     *
     * Detecting the transition from SHUTDOWN to TIDYING is less
     * straightforward than you'd like because the queue may become
     * empty after non-empty and vice versa during SHUTDOWN state, but
     * we can only terminate if, after seeing that it is empty, we see
     * that workerCount is 0 (which sometimes entails a recheck -- see
     * below).
     */    
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }

主池控制状态ctl是一个原子整数,封装了两个概念字段workerCount,表示有效线程数runState,表示是否正在运行、正在关闭等为了打包成一个int,我们限制workerCount为(2^29 )-1(约 5 亿)个线程而不是 (2^31)-1(20 亿)个其他可表示的线程。

如果这在未来成为一个问题,可以将变量更改为 AtomicLong,并调整下面的移位/掩码常量。 但是在需要之前,这段代码使用 int 会更快更简单。 workerCount 是允许启动和不允许停止的工人数量。

该值可能与实际的活动线程数暂时不同,例如当 ThreadFactory 在被询问时未能创建线程时,以及退出线程在终止前仍在执行簿记时。 用户可见的池大小报告为工作人员集的当前大小。

runState 提供主要的生命周期控制,取值:

  • RUNNING:接受新任务并处理排队任务
  • SHUTDOWN:不接受新任务,但处理排队任务
  • STOP:不接受新任务,不处理排队任务,并中断正在进行的任务
  • TIDYING:所有任务都已终止,workerCount 为零,转换到状态 TIDYING 的线程将运行 terminate() 钩子方法
  • TERMINATED: terminate() 已完成这些值之间的数字顺序很重要,以允许有序比较. runState 随时间单调增加,但不需要命中每个状态。

转换是:

RUNNING -> SHUTDOWN

在调用 shutdown() 时,可能隐含在 finalize() 中

(RUNNING 或 SHUTDOWN)-> STOP

在调用 shutdownNow() 时

SHUTDOWN -> TIDYING 当队列和池都为空时

STOP -> TIDYING当池为空时

TIDYING -> TERMINATED

当 terminate() 钩子方法完成时,

在 awaitTermination() 中等待的线程将在状态达到 TERMINATED 时返回。

检测从 SHUTDOWN 到 TIDYING 的转换并不像您想要的那么直接,因为在非空之后队列可能会变空,在 SHUTDOWN 状态期间反之亦然,但是我们只能在看到它为空后看到 workerCount 时才终止是 0

jdk 中的给我印象最深的代码是上面的代码,作者使用二进制的来定义线程的运行状态,如上面的使用了int 类型的值的高 3 位定义了线程的RUNNING,SHUTDOWN、STOP、TIDYING、TERMINATED状态。 后面低的 28 位为线程池可运行的最大运行数量。 同时也在注释中提供了不同状态之间转换代码和评断的当前的状态的方法。如下面注释:

 * RUNNING -> SHUTDOWN
 *    On invocation of shutdown(), perhaps implicitly in finalize()
 * (RUNNING or SHUTDOWN) -> STOP
 *    On invocation of shutdownNow()
 * SHUTDOWN -> TIDYING
 *    When both queue and pool are empty
 * STOP -> TIDYING
 *    When pool is empty
 * TIDYING -> TERMINATED
 *    When the terminated() hook method has completed

让读者在阅读的时候,可以快速理解各种状态之间状态和状态之间转换时使用的方法。

通过以上两点,可以知道

  1. 在自己程序定义程序的运行状态可以使用int 的高m位定义状态,同时低n位存储数量(32=m+n)。
  2. 使用位运算可以节省空间同时提升了代码的运行速度。
  3. 在不同状态的转换过程要提供封装好的方法,方便使用者可以快速了解并使用,避免其直接对细节处理出错,便于团队合作。


ReentrantReadWriteLock的实现

ReentrantReadWriteLock采用读写分离的策略来保证:1.只允许一个线程进行写入;2.没有写入时,多个线程可以读取(ReentrantReadWriteLock内部维护了ReadLock和WriteLock,ReadLock在获取锁时,会先判断WriteLock是否被其他线程占用,WriteLock在获取锁时,也会先判断WriteLock是否被其他线程占用)

理由:1.只用一个state就可以维护读写两种状态,state高16位表示获取读锁的次数(c >>> SHARED_SHIFT),低16位表示获取写锁的次数(c & EXCLUSIVE_MASK),再次领会到了位运算的妙用,以及用一个变量维护两种状态来减少维护成本的思想

2.ReentrantReadWriteLock相比于独占锁(synchronized,ReentrantLock)在读多写少的应用场景下,兼顾线程安全的同时,具有较好的性能,相较于以前了解的多线程分而治之、化整为零的思想,ReentrantReadWriteLock的处理思想拓宽了我对于多线程的视野,在构建一个读多写少场景下线程安全的List,或是协同编辑一份文件,ReentrantReadWriteLock的应用想必是如虎添翼


LongAdder

LongAdder 的主要功能是在并发环境下计数。在并发极高的环境下,性能表现优于 AtomicLong。

 

      public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}
final void longAccumulate(long x, LongBinaryOperator fn,
                          boolean wasUncontended) {
    int h;
    if ((h = getProbe()) == 0) {
        ThreadLocalRandom.current(); // force initialization
        h = getProbe();
        wasUncontended = true;
    }
    boolean collide = false;                // True if last slot nonempty
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        //如果操作的cell 为空,double check 新建 cell
        if ((as = cells) != null && (n = as.length) > 0) {
            if ((a = as[(n - 1) & h]) == null) {
                if (cellsBusy == 0) {       // Try to attach new Cell
                    Cell r = new Cell(x);   // Optimistically create
                    if (cellsBusy == 0 && casCellsBusy()) {
                        boolean created = false;
                        try {               // Recheck under lock
                            Cell[] rs; int m, j;
                            if ((rs = cells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & h] == null) {
                                rs[j] = r;
                                created = true;
                            }
                        } finally {
                            cellsBusy = 0;
                        }
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            }
        // cas 失败 继续循环
        else if (!wasUncontended)       // CAS already known to fail
            wasUncontended = true;      // Continue after rehash
        // 如果 cell cas 成功 break
        else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                     fn.applyAsLong(v, x))))
            break;
        // 如果 cell 的长度已经大于等于 cpu 的数量,扩容意义不大,就不用标记冲突,重试
        else if (n >= NCPU || cells != as)
            collide = false;            // At max size or stale
        else if (!collide)
            collide = true;
        // 获取锁,上锁扩容,将冲突标记为否,继续执行    
        else if (cellsBusy == 0 && casCellsBusy()) {
            try {
                if (cells == as) {      // Expand table unless stale
                    Cell[] rs = new Cell[n << 1];
                    for (int i = 0; i < n; ++i)
                        rs[i] = as[i];
                    cells = rs;
                }
            } finally {
                cellsBusy = 0;
            }
            collide = false;
            continue;                   // Retry with expanded table
        }
        // 没法获取锁,重散列,尝试其他槽
        h = advanceProbe(h);
    }
    // 获取锁,初始化 cell 数组
    else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
        boolean init = false;
        try {                           // Initialize table
            if (cells == as) {
                Cell[] rs = new Cell[2];
                rs[h & 1] = new Cell(x);
                cells = rs;
                init = true;
            }
        } finally {
            cellsBusy = 0;
        }
        if (init)
            break;
    }
    // 表未被初始化,可能正在初始化,回退使用 base。
    else if (casBase(v = base, ((fn == null) ? v + x :
                                fn.applyAsLong(v, x))))
        break;                          // Fall back on using base
}

原因: 实现的思想对于我们设计系统有很大的参考价值。 使用无锁的 cas 操作,提高并发表现。 使用分桶的操作,代码中的 cells 分散竞争。提升代码性能表现。 代码严谨在 cas 的相关操作中都加入了 double check 的操作。 不在一开始就提供系统的最大能力。而是逐步升级。保证代码性能。

看完代码,感叹作者实现的精巧和细致,使用分散竞争,逐步升级的思路解决了极高并发下的性能问题。


AbstractQueuedSynchronizer

jdk最让我触动是AbstractQueuedSynchronizer(抽象的队列同步器)的设计,它是一个公共的同步组件,常常惊叹其设计。其内部核心首先就是拥有了一个代表同步状态的共享变量 即:private volatile int state; 该变量用volatile修饰,保证多线程的内存可见性,它表示的是同步锁状态及数量 用简单的表达式表示就是:state>0?获取到了N个锁:已释放锁。 有了安全强大的共享资源,AQS并没有简单的采用synchronized关键字这种阻塞的方式来实现加锁/释放锁,因为这种方式会让线程都阻塞,导致线程上下文切换很费性能, 而且也不能实现tryLock这样的语义,另外这种方式很依赖JVM底层,说到底是一种限制,会导致很多个性化功能无法实现,比如公平锁和非公平锁。 AQS是通过CAS无锁算法实现的。

/**
 * 以CAS指令原子性的更新State变量,保证在多线程之间安全的更新此值
 * @param expect 期待的值
 * @param update 要更新的新的值
 * @return 如果成功返回true,如果事实的值不等于期待的值返回false
   */
   protected final boolean compareAndSetState(int expect, int update) {
   // See below for intrinsics setup to support this
   return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
   }
   // 这是一个本地方法通过CAS实现无锁的原子性的更新共享变量的值
   public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

AQS采用上述的compareAndSwapInt CAS算法实现高效的更新共享变量。 其实有了state和安全高效的并发更新的方式就可以简单实现多线程的同步了,但AQS并不止于此。 真实的使用场景我们往往是希望线程在争夺同步资源失败后能够自己再重试,并且不要那么频繁的重试,最好是在锁被释放后再去竞争。为此,AQS内部维护一个同步队列, 会将获取同步状态失败的线程以及它当前的同步状态封装为一个Node将其加入同步队列的尾部。

 

 /**
 * 将当前线程加入到等待队列队尾,并返回当前线程所在节点.
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
   */
   private Node addWaiter(Node mode) {
   //创建节点以当前线程和给定模式为参数
   Node node = new Node(Thread.currentThread(), mode);
   //先尝试一下直接放到队尾,普通的链表操作.
   Node pred = tail;
   if (pred != null) {
       node.prev = pred;
       if (compareAndSetTail(pred, node)) {
           pred.next = node;
           return node;
       }
   }
   //尝试失败则通过enq入队。
   enq(node);
   return node;
   }

 

可以看到这段代码也用一个CAS的安全设置同步队列尾部的方法compareAndSetTail。节点加入到同步队列后,就会进入一个自旋竞争资源的过程,大概就是先拿到当前线程在队列的前驱节点,如果这个前驱节点恰好是头结点,自己就是下一个那么就尝试去获取资源,如果获取不到判断自己可以休息,就wating,等待被unpark继续上述流程。 AQS作为一个队列同步器框架,采用了模板设计模式,里面的tryAcquire、tryAcquireShare分别是独占模式和共享模式的扩展点,交由子类去实现的,决定能不能重入,怎样才算加锁成功,我们就可以很方便实现不同功能的同步锁,比如JDK已经实现的CountDownLatch、ReentrantLock等,当然我们在工作中有需要也可以定制实现满足特殊业务场景需求的锁。这也给我了震撼和很强的启发,我也因此坚信抽象能力是优秀程序员的一种必备能力。

 

ThreadLocal基于数组结构的拉链法存储

印象最深的是ThreadLocal基于数组结构的拉链法存储。因为面试的时候,面试官问到了,而当时我不知道,所以下来之后就自己看了一下。因为斐波那契散列的非常均匀,普通散列到15个以后已经开发生产碰撞。这也就是斐波那契散列的魅力,减少碰撞也就可以让数据存储的更加分散,获取数据的时间复杂度基本保持在O(1)。

//通常的hashcode
public int hashCode() {  
 int h = hash;  
    int len = count;  
 if (h == 0 && len > 0) {  
    int off = offset;  
    char val[] = value;  
           for (int i = 0; i < len; i++) {  
               h = 31*h + val[off++];  
           }  
           hash = h;  
       }  
       return h;  
} 
//基于斐波那契(Fibonacci)散列法 + 拉链法
private static final int HASH_INCREMENT = 0x61c88647; 
private static int nextHashCode() { 
    return nextHashCode.getAndAdd(HASH_INCREMENT); 
}
f(k) = ((k * 2654435769) >> X) << Y
//对于常见的32位整数而言,也就是 
  f(k) = (k * 2654435769) >> 28


目录
相关文章
|
7月前
|
安全 前端开发 Java
JDK源码级别彻底剖析JVM类加载机制
JDK源码级别彻底剖析JVM类加载机制
|
7月前
|
缓存 Dubbo Java
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
趁同事上厕所的时间,看完了 Dubbo SPI 的源码,瞬间觉得 JDK SPI 不香了
|
4月前
|
算法 安全 Java
深入JDK源码:揭开ConcurrentHashMap底层结构的神秘面纱
【8月更文挑战第24天】`ConcurrentHashMap`是Java并发编程中不可或缺的线程安全哈希表实现。它通过精巧的锁机制和无锁算法显著提升了并发性能。本文首先介绍了早期版本中使用的“段”结构,每个段是一个带有独立锁的小型哈希表,能够减少线程间竞争并支持动态扩容以应对高并发场景。随后探讨了JDK 8的重大改进:取消段的概念,采用更细粒度的锁控制,并引入`Node`等内部类以及CAS操作,有效解决了哈希冲突并实现了高性能的并发访问。这些设计使得`ConcurrentHashMap`成为构建高效多线程应用的强大工具。
56 2
|
设计模式 Java 程序员
太爆了!阿里最新出品2023版JDK源码学习指南,Github三天已万赞
最近后台收到很多粉丝私信,说的是程序员究竟要不要去读源码?当下行情,面试什么样的薪资/岗位才会被问到源码? 对此,我的回答是:一定要去读,并且要提到日程上来! 据不完全统计,现在市面上不管是初级,中级,还是高级岗,面试的时候都有可能会问到源码中的问题,它已经成为程序员常规必备的一个技术点。如果你当下想通过一个面试,或者想把中级薪资要到相对于比较高的话,源码这块就必须要会。
146 0
|
6月前
|
Java Spring
深入解析Spring源码,揭示JDK动态代理的工作原理。
深入解析Spring源码,揭示JDK动态代理的工作原理。
62 0
|
7月前
|
设计模式 Java
根据JDK源码Calendar来看工厂模式和建造者模式
根据JDK源码Calendar来看工厂模式和建造者模式
|
7月前
|
算法 Java 索引
【数据结构与算法】4、双向链表(学习 jdk 的 LinkedList 部分源码)
【数据结构与算法】4、双向链表(学习 jdk 的 LinkedList 部分源码)
70 0
|
7月前
|
消息中间件 Oracle Dubbo
Netty 源码共读(一)如何阅读JDK下sun包的源码
Netty 源码共读(一)如何阅读JDK下sun包的源码
132 1
|
7月前
|
Java Linux iOS开发
Spring5源码(27)-静态代理模式和JDK、CGLIB动态代理
Spring5源码(27)-静态代理模式和JDK、CGLIB动态代理
52 0
|
7月前
|
算法 安全 Java
ConcurrentLinkedQueue的源码解析(基于JDK1.8)
ConcurrentLinkedQueue的源码解析(基于JDK1.8) ConcurrentLinkedQueue是Java集合框架中的一种线程安全的队列,它是通过CAS(Compare and Swap)算法实现的并发队列。在并发场景下,ConcurrentLinkedQueue能够保证队列的线程安全性,同时性能也很不错。