zoukankan      html  css  js  c++  java
  • java编程之美——高级篇(二)

    一、Java并发包中原子操作类原理剖析

    JUC包提供了一系列的原子性操作类,这些类都是使用非阻塞CAS算法实现原子性的,使用CAS算法可以提高并发性。下面以AtomicLong、Long Adder、和Long Acccumulator为例。

    1.原子变量操作类

    JUC并发包中包含有AtomicLong、AtomicInteger、AtomicBoolean,内部都是用Unsafe实现。

     AtomicLong中的主要函数:

    (1)递增和递减操作代码

    原理都是通过Unsafe实现的。弄懂上一篇https://www.cnblogs.com/feifei123/p/12590197.html的Unsafe部分就不难懂了。

    (2)

     对象中偏移量为valueOffset的值为expect,则更新为update.

    但是高并发的情况下,AtomicLong 的性能也并不高。JDK8提供了性能更好的LongAdder类

    2.JDK8新增的LongAdder

    LongAddder解决性能问题的思想?

    因为AtomicLong,在高并发下大量线程会去竞争同一个原子变量。但只有一个线程会成功执行CAS,竞争失败,会让大量线程进行自旋尝试,白白浪费CUP资源。LongAdder通过把一个变量分解为多个变量,让同样多的线程来竞争,这就解决了性能问题。

    LongAdder内部维护多个Cell,每一个Cell有一个初始为0的long变量。Cell类型是对AtomicLong的改进,用来减少对缓存的争用,也就是解决伪共享问题。

    对于孤立的多个原子操作进行字节填充是浪费的,因为原子性操作都是无规律地分散在内存中,多个原子被放进同一个缓存行的可能性很少。但是原子性数组元素的内存地址是连续的,所以需要使用@sun.misc.Contended对Cell类进行字节填充。

    LongAdder源码分析:

    首先,LongAdder继承Stripped64,在Stripped64内部维护着三个变量cells,base,cellsBusy,LongAdder的真实值其实是base和cells所有值的累加。当创建Cell元素,扩容Cell数组,或者进行初始化Cell数组,使用CAS操作该变量来保证同时只有一个线程可以进行其中之一的操作。

    base是个基础值。默认值为0,cellsBusy用来实现自旋锁。状态值只有0和1

     接下来,看看Cell的结构

     Cell内部内部维护一个volatile变量,因为一个线程对cell值的修改需要对其他线程可见。另外cas函数保证保证当前线程更新时被分配的cell元素中value值的原子性。该类使用@sun.misc.Contended注解,为了解决cell数组的伪共享问题。

    LongAdder主要方法:

    long sum()

    void reset()

    long sumThenReset()

    long longValue()

    上面的不在分析,读者可以自行阅读,都不难理解。下面主要看下add方法的实现。

    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;
    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;
    }
    else if (!wasUncontended) // CAS already known to fail
    wasUncontended = true; // Continue after rehash
    else if (a.cas(v = a.value, ((fn == null) ? v + x :
    fn.applyAsLong(v, x))))
    break;
    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);
    }
    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;
    }
    else if (casBase(v = base, ((fn == null) ? v + x :
    fn.applyAsLong(v, x))))
    break; // Fall back on using base
    }
    }

     LongAccumulator类原理探究

    LongAccumulator比LongAdder功能更强,相比于LongAdder,可以为累加器提供非0的默认值。还可以指定累加规则。

     二、Java并发包中并发List源码剖析

    并发包中的并发List只有CopyOnWriteArrayList。CopyOnWriteArrayList是一个线程安全的ArrayList。对其进行的修改操作都是在底层的一个复制的数组上进行的。也就是使用了写时复制策略。

    如果让我们自己做一个写时复制的线程安全的list,有哪些需要考虑?

    1.何时初始化list,初始化list元素个数为多少?list有大小限制吗?

    2.如何保证线程安全?

    3如何保证遍历list的数据一致性?

    三、Java并发包中锁原理解析

    1.LockSupport工具类

    LockSupport是rt.jar包的一个工具类,主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础。

    LockSupport类与每个使用它的线程都会关联一个许可证。默认情况下,调用LOckSupport类的方法的线程是不持有许可证的。LockSupport是使用Unsafe类实现的。

    主要方法:

    (1) void park() 如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则会立刻返回。否则,该调用线程会被阻塞挂起。

    在其他线程调用了unpark() interrupt() 或者被虚假唤醒,则阻塞线程也会返回。

    (2) void unpark(Thread thread)

    当一个线程调用unpark时,如果thread线程没有持有LockSupport许可证,该让该线程持有。如果因为调用park被挂起,则调用unpark后,该线程会被唤醒。如果thread之前没有调用park,则调用unpark方法后,在调用park方法,其会立刻返回。

    (3) void parkNanos(long nanos) 阻塞一段时间

    另外,park方法还支持带有blocker参数的方法,park(blocker),可以打印更详细的堆栈信息。jstack pid

    (4)park(blocker)

    (5) parkNanos(Object blocker,Long nanos)

    (6) parkUntil(Object object,Long deadline)

    -------------------------------------------------------------------下篇待续

  • 相关阅读:
    windows10 gcc编译C程序(简单编译)
    windows10 下 gcc/g++ 的安装
    神经网络内在逻辑:没打开的AI“黑匣子”
    tensorflow的变量作用域
    如何手画三相电相(线)电压(流)波形图
    pycharm如何添加固定代码块
    pycharm的个性化设置
    zookeeper学习三
    zookeeper学习二
    es中对mapping的理解
  • 原文地址:https://www.cnblogs.com/feifei123/p/12595426.html
Copyright © 2011-2022 走看看