Maison  >  Article  >  Java  >  Introduction détaillée aux améliorations de CAS dans Java8

Introduction détaillée aux améliorations de CAS dans Java8

黄舟
黄舟original
2017-03-24 10:48:401802parcourir

Il y a quelques jours, j'ai accidentellement exécuté le code que j'avais écrit auparavant pour tester les performances d'incrémentation automatique d'AtomicInteger et synchronisé. J'ai découvert de manière inattendue que les performances d'AtomicInteger étaient meilleures que celles de synchronisation. Après quelques recherches pour la raison, j'ai. Trouvé comme suit :

Dans jdk1.7, le getAndIncrement d'AtomicInteger est comme ceci :

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

Et dans jdk1.8, c'est comme ceci :

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

On peut voir que dans jdk1.8, la méthode getAndAddInt de Unsafe est utilisée directement, mais dans Unsafe de jdk1.7, une telle méthode n'existe pas. (PS : afin de découvrir la raison, j'ai décompilé Unsafe et j'ai découvert que la nouvelle tentative échouée de CAS avait été effectuée dans la méthode getAndAddInt. J'ai utilisé la réflexion pour obtenir l'instance Unsafe et j'ai écrit le même code que getAndAddInt, mais les résultats du test étaient le même que getAndIncrement de jdk1 .7 est tout aussi lent. Je ne sais pas quel type de magie noire est jouée dans Unsafe. Veuillez me donner quelques conseils d'experts.) (Supplémentaire : il y a une inférence à la fin de l'article).

En regardant le code source d'AtomicInteger, on peut constater qu'il est affecté. Il existe également la plupart des méthodes telles que getAndAdd et addAndGet.

Avec cette amélioration du CAS, nous avons une autre raison d'utiliser des algorithmes non bloquants.

Enfin, le code de test est donné. Il convient de noter que cette méthode de test est simple et grossière. Les performances de compareAndSet ne sont pas aussi bonnes que synchronisées. Il existe des différences dans la manière dont ils sont utilisés. De plus, dans l'utilisation réelle, il existe également un traitement commercial, et il est impossible d'avoir une intensité de concurrence aussi élevée. Cette comparaison n'est utilisée qu'à titre de référence. les performances d'AtomicInteger.getAndIncrement ont été grandement améliorées.

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

Voici les résultats des tests sous le processeur Intel(R) Core(TM) i7-4710HQ à 2,50 GHz (quatre cœurs et huit threads) (la fluctuation est faible, donc seulement quatre ou cinq des chaque élément a été testé plusieurs fois, prenez l'une des valeurs les plus intermédiaires) :

jdk1.7

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

jdk1.8

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

Supplément : fourni ici à à la demande des internautes Le code source pertinent de Unsafe.getAndAddInt et mon code de test.

Utilisez jad pour décompiler le code source d'Unsafe dans 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);

Le code source Unsafe d'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);

Mon code de test (astuce : si eclipseEn attente que l'EDI signale une erreur, c'est parce que le niveau restreint Unsafe est utilisé. Le niveau d'avertissement peut être réduit d'erreur à avertissement, en particulier 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;
	}
	...
}
Supplément 2 : Pour Les raisons de l'amélioration des performances sont les suivantes. Bien que je ne puisse pas dire que c'est correct à 100 % (car le code source de jvm n'est pas utilisé comme argument), j'en suis quand même très sûr. @zhoukeren@liuxinglanyue !

Unsafe est spécialement traité et ne peut pas être compris comme du code Java normal. La différence est la suivante :

Lors de l'appel de getAndAddInt, si la couche inférieure du système prend en charge la récupération et l'ajout, elle s'exécute. C'est la méthode native, utilisant fetch-and-add ;

Si elle n'est pas prise en charge, suivez simplement le corps de la méthode getAndAddInt vu ci-dessus et exécutez-la sous forme de code java, en utilisant compare-and-swap ;
Cela coïncide également avec l'annotation

ci-dessus Unsafe::getAndAddInt dans openjdk8 :

Le traitement spécial de Unsafe est ce que j'ai mentionné ci-dessus.
// The following contain CAS-based Java implementations used on
// platforms not supporting native instructions

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn