Heim  >  Artikel  >  Java  >  Detaillierte Einführung in die Erweiterungen von CAS in Java8

Detaillierte Einführung in die Erweiterungen von CAS in Java8

黄舟
黄舟Original
2017-03-24 10:48:401796Durchsuche

Vor ein paar Tagen habe ich versehentlich den Code ausgeführt, den ich zuvor geschrieben habe, um die automatische Inkrementierungsleistung von AtomicInteger zu testen, und habe unerwartet festgestellt, dass die Leistung von AtomicInteger besser ist als die synchronisierte Gefunden wie folgt:

In jdk1.7 ist getAndIncrement von AtomicInteger so:

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

Und in jdk1.8 ist es so:

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

Es ist ersichtlich, dass in jdk1.8 die getAndAddInt-Methode von Unsafe direkt verwendet wird, in Unsafe von jdk1.7 jedoch keine solche Methode. (PS: Um den Grund herauszufinden, habe ich Unsafe dekompiliert und festgestellt, dass der fehlgeschlagene Wiederholungsversuch von CAS in der getAndAddInt-Methode durchgeführt wurde. Ich habe Reflection verwendet, um die Unsafe-Instanz abzurufen, und den gleichen Code wie getAndAddInt geschrieben, aber die Testergebnisse waren Das gleiche wie getAndIncrement von jdk1.7 ist genauso langsam. Ich weiß nicht, welche Art von schwarzer Magie in Unsafe gespielt wird. (Zusätzlich: Es gibt eine Schlussfolgerung am Ende des Artikels.)

Wenn wir uns den Quellcode von AtomicInteger ansehen, können wir feststellen, dass es auch die meisten Methoden wie getAndAdd und addAndGet betrifft.

Mit dieser Erweiterung von CAS haben wir einen weiteren Grund, nicht blockierende Algorithmen zu verwenden.

Abschließend wird der Testcode angegeben. Es ist zu beachten, dass die Leistung von CompareAndSet nicht so gut ist wie die von Synchronized Darüber hinaus gibt es Unterschiede in der Art und Weise, wie sie verwendet werden, und es ist unmöglich, eine so hohe Wettbewerbsintensität zu haben. Dieser Vergleich dient nur als Referenz Die Leistung von AtomicInteger.getAndIncrement wurde erheblich verbessert.

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();
	}
}

Das Folgende sind die Testergebnisse unter Intel(R) Core(TM) i7-4710HQ CPU bei 2,50 GHz (vier Kerne und acht Threads) (die Schwankung ist gering, also nur vier oder fünf davon). Jedes Element wurde einmal getestet, nehmen Sie einen der Zwischenwerte):

jdk1.7

AtomicInteger.getAndIncrement 12.653.757.034
synchronized 4.146.813.462
AtomicInteger. CompareAndSet 12.952.821.234 12.893.188.541

Ergänzung: Hier bereitgestellt unter Auf Anfrage der Internetnutzer Der relevante Quellcode von Unsafe.getAndAddInt und mein Testcode. Verwenden Sie jad, um den Quellcode von Unsafe in jdk1.8 zu dekompilieren:


Der Unsafe-Quellcode von openjdk8:

Mein Testcode (Tipp : Wenn

Eclipse

darauf wartet, dass die IDE einen Fehler meldet, liegt dies daran, dass die eingeschränkte Unsafe-Warnstufe von „Fehler“ auf „Warnung“ reduziert werden kann, insbesondere Baidu):

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);

Ergänzung 2: Die Gründe für die Leistungsverbesserung sind wie folgt: Obwohl ich nicht sagen kann, dass sie zu 100 % korrekt ist (da der Quellcode von jvm nicht als Argument verwendet wird), bin ich mir dennoch sehr sicher. Vielen Dank an Netizen @zhoukeren@liuxinglanyue!

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);
Unsicher wird speziell verarbeitet und kann nicht als normaler Java-Code verstanden werden. Der Unterschied ist:

Wenn getAndAddInt aufgerufen wird und die unterste Ebene des Systems das Abrufen und Hinzufügen unterstützt, wird es ausgeführt Es handelt sich um die native Methode, die „fetch-and-add“ verwendet. Wenn sie nicht unterstützt wird, folgen Sie einfach dem oben gezeigten getAndAddInt-Methodenkörper und führen Sie ihn in Form von Java-Code aus, indem Sie „compare-and-swap“ verwenden Dies stimmt auch mit der

Anmerkung
...
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;
	}
	...
}
oben Unsafe::getAndAddInt in openjdk8 überein:

Die spezielle Verarbeitung von Unsafe ist das, was ich oben erwähnt habe.

Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in die Erweiterungen von CAS in Java8. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn