>  기사  >  Java  >  Java 동시 프로그래밍에서 LongAdder의 실행 상태는 무엇입니까?

Java 동시 프로그래밍에서 LongAdder의 실행 상태는 무엇입니까?

WBOY
WBOY앞으로
2023-05-09 12:52:15718검색

longAccumulate 메소드

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
    }
}

코드가 길다. 섹션별로 분석해 보자. 먼저 각 부분의 내용을 소개한다

  • 1부: for 루프 앞의 코드, 주로 해시를 얻기 위한 코드 스레드 값, 0이면 강제 초기화for循环之前的代码,主要是获取线程的hash值,如果是0的话就强制初始化

  • 第二部分:for循环中第一个if语句,在Cell数组中进行累加、扩容

  • 第三部分:for循环中第一个else if语句,这部分的作用是创建Cell数组并初始化

  • 第四部分:for循环中第二个else if语句,当Cell数组竞争激烈时尝试在base上进行累加

线程hash值

int h; 
if ((h = getProbe()) == 0) { 
    ThreadLocalRandom.current(); // force initialization 
    h = getProbe(); 
    wasUncontended = true;   // true表示没有竞争
} 
boolean collide = false; // True if last slot nonempty 可以理解为是否需要扩容

这部分的核心代码是getProbe方法,这个方法的作用就是获取线程的hash值,方便后面通过位运算定位到Cell数组中某个位置,如果是0的话就会进行强制初始化

初始化Cell数组

final void longAccumulate(long x, LongBinaryOperator fn,
                          boolean wasUncontended) {
    // 省略...
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        if ((as = cells) != null && (n = as.length) > 0) {
            // 省略...
        }
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) {  // 获取锁
            boolean init = false;  // 初始化标志
            try {                           // Initialize table
                if (cells == as) {
                    Cell[] rs = new Cell[2];  // 创建Cell数组
                    rs[h & 1] = new Cell(x);  // 索引1位置创建Cell元素,值为x=1
                    cells = rs;   // cells指向新数组
                    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
    }
}

第一种情况下Cell数组为null,所以会进入第一个else if语句,并且没有其他线程进行操作,所以cellsBusy==0cells==as也是truecasCellsBusy()尝试对cellsBusy进行cas操作改成1也是true

首先创建了一个有两个元素的Cell数组,然后通过线程h & 1 的位运算在索引1的位置设置一个value1Cell,然后重新赋值给cells,标记初始化成功,修改cellsBusy0表示释放锁,最后跳出循环,初始化操作就完成了。

对base进行累加

final void longAccumulate(long x, LongBinaryOperator fn,
                          boolean wasUncontended) {
    // 省略...
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        if ((as = cells) != null && (n = as.length) > 0) {
            // 省略...
        }
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
            // 省略...
        }
        else if (casBase(v = base, ((fn == null) ? v + x :
                                    fn.applyAsLong(v, x))))
            break;                          // Fall back on using base
    }
}

第二个else if语句的意思是当Cell数组中所有位置竞争都很激烈时,就尝试在base上进行累加,可以理解为最后的保障

Cell数组初始化之后

final void longAccumulate(long x, LongBinaryOperator fn,
                          boolean wasUncontended) {
    // 省略...
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        if ((as = cells) != null && (n = as.length) > 0) {  // as初始化之后满足条件
            if ((a = as[(n - 1) & h]) == null) {  // as中某个位置的值为null
                if (cellsBusy == 0) {       // Try to attach new Cell 是否加锁
                    Cell r = new Cell(x);   // Optimistically create 创建新Cell
                    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) {  // 重新检查该位置是否为null
                                rs[j] = r;  // 该位置添加Cell元素
                                created = true;  // 新Cell创建成功
                            }
                        } finally {
                            cellsBusy = 0;  // 释放锁
                        }
                        if (created)
                            break;  // 创建成功,跳出循环
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;  // 扩容标志
            }
            else if (!wasUncontended)       // 上面定位到的索引位置的值不为null
                wasUncontended = true;      // 重新计算hash,重新定位其他索引位置重试
            else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                         fn.applyAsLong(v, x))))  // 尝试在该索引位置进行累加
                break;
            else if (n >= NCPU || cells != as)  // 如果数组长度大于等于CPU核心数,就不能在扩容
                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];  // 创建一个原来长度2倍的数组
                        for (int i = 0; i < n; ++i)
                            rs[i] = as[i];  // 把原来的元素拷贝到新数组中
                        cells = rs;  // cells指向新数组
                    }
                } finally {
                    cellsBusy = 0;  // 释放锁
                }
                collide = false;  // 已经扩容完成,修改标志不用再扩容
                continue;                   // Retry with expanded table
            }
            h = advanceProbe(h);  // 重新获取hash值
        }
        // 省略...
}

根据代码中的注释分析一遍整体逻辑

  • 首先如果找到数组某个位置上的值为null,说明可以在这个位置进行操作,就创建一个新的Cell并初始化值为1放到这个位置,如果失败了就重新计算hash值再重试

  • 定位到的位置已经有值了,说明线程之间产生了竞争,如果wasUncontendedfalse就修改为true,并重新计算hash重试

  • 定位的位置有值并且wasUncontended已经是true,就尝试在该位置进行累加

  • 当累加失败时,判断数组容量是否已经达到最大,如果是就不能进行扩容,只能rehash并重试

  • 如果前面条件都不满足,并且扩容标志collide标记为false的话就修改为true,表示可以进行扩容,然后rehash重试

  • 首先尝试加锁,成功了就进行扩容操作,每次扩容长度是之前的2

파트 2: Cell에 있는 <code>for 루프의 첫 번째 if 문 code> 배열 누적 및 확장 🎜🎜🎜🎜3부: for 루프의 첫 번째 else if 문 이 부분의 기능은 Cell을 만드는 것입니다. 배열 및 초기화 🎜🎜🎜🎜4부: Cell에 대한 경쟁이 발생하는 경우 for 루프의 두 번째 else if 문 > 배열이 치열하니 base🎜🎜🎜🎜thread hash value🎜rrreee🎜에 누적해 보세요. 이 부분의 핵심 코드는 getProbe 메소드입니다. 스레드 /code> 값의 hash0이면 비트 연산을 통해 <code>Cell 배열의 특정 위치를 찾는 것이 편리합니다. code>, 강제 초기화가 수행됩니다🎜🎜Cell 배열 초기화🎜rrreee🎜 첫 번째 경우 Cell 배열이 null이므로 첫 번째 else if 문이 입력되고 다른 스레드는 작동하지 않으므로 cellsBusy==0, cells==astrue입니다. , casCellsBusy()cellsBusy code>cas 작업을 수행하고 1로 변경하려고 시도합니다. 이는 이기도 합니다. >사실. 🎜🎜먼저 두 개의 요소로 구성된 Cell 배열을 생성한 다음 스레드 h & 1의 비트 연산을 통해 인덱스 1 위치에 설정합니다. > 1이고 에 다시 할당된 , 초기화를 성공적으로 표시하고 cellsBusy 는 잠금을 해제하고 마지막으로 루프에서 벗어나 초기화 작업이 완료되는 0입니다. 🎜🎜Accumulate base🎜rrreee🎜두 번째 else if 문은 Cell 배열의 모든 위치에 대한 경쟁이 치열할 때 Accumulation on base를 시도할 수 있음을 의미합니다. 최종 보증으로 이해하세요🎜🎜Cell 배열이 초기화된 후🎜rrreee🎜코드의 주석에 따라 전체 로직을 분석합니다🎜🎜🎜🎜우선 배열의 특정 위치에 있는 값이 다음과 같다고 판단되면 null - 이 위치에서 작업을 수행할 수 있음을 나타냅니다. 새 Cell을 만들고 값을 1로 초기화하여 이 위치에 배치합니다. .실패하면 다시 시도하세요. 해시 값을 계산하고 다시 시도하세요. 🎜🎜🎜🎜위치에 이미 값이 있어 wasUncontended인 경우]입니다. >가 false code>이면 <code>true로 변경되고 hash를 다시 계산하고 다시 시도합니다 🎜🎜🎜🎜위치 위치에 값이 있고 wasUncontended 가 이미 true이면 해당 위치에 누적을 시도해보세요🎜🎜🎜🎜 누적에 실패하면 어레이 용량이 최대치에 도달했는지 확인하세요. 그렇다면 용량을 확장할 수 없습니다. 재해시만 하고 다시 시도할 수 있습니다🎜 🎜🎜🎜이전 조건 중 어느 것도 충족되지 않고 확장 플래그 충돌false로 표시되는 경우, 확장이 수행될 수 있음을 나타내는 true로 변경한 다음 rehashRetry🎜🎜🎜🎜먼저 잠금을 시도합니다. 성공하면 확장 작업이 각각 수행됩니다. 확장 길이가 이전 배열의 2배이고 원래 배열 내용이 새 배열에 복사되면 확장이 완료됩니다. 🎜🎜🎜

위 내용은 Java 동시 프로그래밍에서 LongAdder의 실행 상태는 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제