Home  >  Article  >  Java  >  Detailed introduction to the enhancements of CAS in Java8

Detailed introduction to the enhancements of CAS in Java8

黄舟
黄舟Original
2017-03-24 10:48:401802browse

A few days ago, I accidentally ran the code I wrote previously to test the auto-increment performance of AtomicInteger and synchronized. I unexpectedly found that the performance of AtomicInteger is better than synchronized. After searching for the reasons, I found The following is found:

In jdk1.7, the getAndIncrement of AtomicInteger is like this:

public final int getAndIncrement() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return current;
    }
}
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

And in jdk1.8, it is like this:

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

It can be seen that , in jdk1.8, the getAndAddInt method of Unsafe is used directly, but in Unsafe of jdk1.7, there is no such method. (PS: In order to find out the reason, I decompiled Unsafe and found that the failed retry of CAS was done in the getAndAddInt method. I used reflection to get the Unsafe instance and wrote the same code as getAndAddInt, but the test results were the same as jdk1 .7's getAndIncrement is just as slow. I don't know what kind of black magic is played in Unsafe. Please give me some advice.) (Supplement: There is an inference at the end of the article)

You can find out by looking at the source code of AtomicInteger that the affected There are also most methods such as getAndAdd and addAndGet.

With this enhancement to CAS, we have another reason to use non-blocking algorithms.

Finally, the test code is given. It should be noted that this test method is simple and crude. The performance of compareAndSet is not as good as synchronized. It cannot simply be said that synchronized is better. There are differences in how they are used. Moreover, in actual use, there is also business processing, and it is impossible to have such a high competition intensity. This comparison is only used as a reference. What this test can prove is that the performance of AtomicInteger.getAndIncrement has been greatly improved.

package performance;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

public class AtomicTest {
	//测试规模,调用一次getAndIncreaseX视作提供一次业务服务,记录提供TEST_SIZE次服务的耗时
	private static final int TEST_SIZE = 100000000;
	//客户线程数
	private static final int THREAD_COUNT = 10;
	//使用CountDownLatch让各线程同时开始
	private CountDownLatch cdl = new CountDownLatch(THREAD_COUNT + 1);

	private int n = 0;
	private AtomicInteger ai = new AtomicInteger(0);
	private long startTime;

	public void init() {
		startTime = System.nanoTime();
	}

	/**
	 * 使用AtomicInteger.getAndIncrement,测试结果为1.8比1.7有明显性能提升
	 * @return
	 */
	private final int getAndIncreaseA() {
		int result = ai.getAndIncrement();
		if (result == TEST_SIZE) {
			System.out.println(System.nanoTime() - startTime);
			System.exit(0);
		}
		return result;
	}

	/**
	 * 使用synchronized来完成同步,测试结果为1.7和1.8几乎无性能差别
	 * @return
	 */
	private final int getAndIncreaseB() {
		int result;
		synchronized (this) {
			result = n++;
		}
		if (result == TEST_SIZE) {
			System.out.println(System.nanoTime() - startTime);
			System.exit(0);
		}
		return result;
	}

	/**
	 * 使用AtomicInteger.compareAndSet在java代码层面做失败重试(与1.7的AtomicInteger.getAndIncrement的实现类似),
	 * 测试结果为1.7和1.8几乎无性能差别
	 * @return
	 */
	private final int getAndIncreaseC() {
		int result;
		do {
			result = ai.get();
		} while (!ai.compareAndSet(result, result + 1));
		if (result == TEST_SIZE) {
			System.out.println(System.nanoTime() - startTime);
			System.exit(0);
		}
		return result;
	}

	public class MyTask implements Runnable {
		@Override
		public void run() {
			cdl.countDown();
			try {
				cdl.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			while (true)
				getAndIncreaseA();// getAndIncreaseB();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		AtomicTest at = new AtomicTest();
		for (int n = 0; n < THREAD_COUNT; n++)
			new Thread(at.new MyTask()).start();
		System.out.println("start");
		at.init();
		at.cdl.countDown();
	}
}

The following are the test results under Intel(R) Core(TM) i7-4710HQ CPU @2.50GHz (four cores and eight threads) (the fluctuation is small, so each item was only tested four or five times. Take one of the more intermediate values):

jdk1.7

AtomicInteger.getAndIncrement 12,653,757,034
synchronized 4,146,813,462
AtomicInteger.compareAndSet 12,952,821,234

jdk1.8

AtomicInteger.getAndIncrement 2,159,486,620
synchronized 4,067,309,911
AtomicInteger.compareAndSet 12,893,188,541

Supplement: At the request of netizens, Unsafe is provided here. The relevant source code of getAndAddInt and my test code.

Use jad to decompile the source code of Unsafe in jdk1.8:

public final int getAndAddInt(Object obj, long l, int i)
{
    int j;
    do
        j = getIntVolatile(obj, l);
    while(!compareAndSwapInt(obj, l, j, j + i));
    return j;
}
public native int getIntVolatile(Object obj, long l);
public final native boolean compareAndSwapInt(Object obj, long l, int i, int j);

Unsafe source code of openjdk8:

public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}
public native int     getIntVolatile(Object o, long offset);
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);

My test code (tip: if eclipse When the IDE reports an error, it is because of the use of restricted Unsafe. You can reduce the warning level from error to warning (specifically Baidu):

...
import sun.misc.Unsafe;
public class AtomicTest {
	....
	private Unsafe unsafe;
	private long valueOffset;
	public AtomicTest(){
		Field f;
		try {
			f = Unsafe.class.getDeclaredField("theUnsafe");
			f.setAccessible(true);
			unsafe = (Unsafe)f.get(null);
			valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
		}catch(NoSuchFieldException e){
		...
		}
	}
	private final int getAndIncreaseD(){
		int result;
		do{
			result = unsafe.getIntVolatile(ai, valueOffset);
		}while(!unsafe.compareAndSwapInt(ai, valueOffset, result, result+1));
		if(result == MAX){
			System.out.println(System.nanoTime()-startTime);
			System.exit(0);
		}
		return result;
	}
	...
}

Supplementary 2: For the reasons for performance improvement, there are Although I cannot say that the following inference is 100% correct (because the source code of jvm is not used as an argument), I am still very confident. Thank you to netizen @zhoukeren@liuxinglanyue!

Unsafe is specially processed and cannot be understood as regular java code. The difference is:

When calling getAndAddInt, if the bottom layer of the system supports fetch-and-add, then it executes It is the native method, using fetch-and-add;
If it is not supported, just follow the getAndAddInt method body seen above and execute it in the form of java code, using compare-and-swap;

This also coincides with the comment above Unsafe::getAndAddInt in openjdk8:

// The following contain CAS-based Java implementations used on
// platforms not supporting native instructions

The special processing of Unsafe is the "black magic" I mentioned above ".

The above is the detailed content of Detailed introduction to the enhancements of CAS in Java8. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn