2010-06-01 4 views
48

J'ai jeté un oeil dans OpenJDK source code de CopyOnWriteArrayList et il semble que toutes les opérations d'écriture sont protégées par le même verrou et les opérations de lecture ne sont pas protégées du tout. Si je comprends bien, sous JMM tous les accès à une variable (à la fois en lecture et en écriture) doivent être protégés par des verrous ou des effets de réorganisation peuvent se produire.Comment CopyOnWriteArrayList peut-il être thread-safe?

Par exemple, la méthode set(int, E) contient ces lignes (sous les verrous):

/* 1 */ int len = elements.length; 
/* 2 */ Object[] newElements = Arrays.copyOf(elements, len); 
/* 3 */ newElements[index] = element; 
/* 4 */ setArray(newElements); 

La méthode get(int), d'autre part, seulement return get(getArray(), index);. Dans ma compréhension de JMM, cela signifie que get peut observer le tableau dans un état incohérent si les instructions 1-4 sont réordonnées comme 1-2 (nouveau) -4-2 (copyOf) -3.

Est-ce que je comprends mal JMM ou existe-t-il d'autres explications sur pourquoi CopyOnWriteArrayList est thread-safe?

Répondre

61

Si vous regardez la référence du tableau sous-jacent, vous verrez qu'elle est marquée volatile. Lorsqu'une opération d'écriture se produit (comme dans l'extrait ci-dessus), cette référence volatile est uniquement mise à jour dans l'instruction finale via setArray. Jusqu'à ce point, toutes les opérations de lecture renverront des éléments du ancienne copie du tableau.

Le point important est que la mise à jour du tableau est une opération atomique et, par conséquent, les lectures verront toujours le tableau dans un état cohérent. L'avantage de ne retirer un verrou que pour les opérations d'écriture est un débit amélioré pour les lectures: En effet, les opérations d'écriture pour un CopyOnWriteArrayList peuvent être très lentes car elles impliquent la copie de la liste entière.

+0

Merci. J'ai raté le fait que le tableau est "volatile". – Fixpoint

+15

Un détail important est que volatile ne s'applique qu'à la référence du tableau elle-même, et non au contenu du tableau. Cependant, comme toutes les modifications apportées au tableau sont effectuées ** avant ** sa référence est publiée, les garanties volatiles s'étendent au contenu du tableau. – assylias

16

L'obtention de la référence du tableau est une opération atomique. Ainsi, les lecteurs verront l'ancien tableau ou le nouveau tableau - de toute façon l'état est cohérent. (set(int,E) calcule le nouveau contenu du tableau avant de la référence, de sorte que le tableau est cohérent lorsque la asignment est faite.)

Le tableau lui-même référence est marqué comme volatile afin que les lecteurs ne ont pas besoin d'utiliser un verrou pour voir les changements à le tableau référencé. (EDIT: En outre, volatile garantit que l'affectation n'est pas réorganisée, ce qui conduirait à l'affectation effectuée lorsque le tableau est probablement dans un état incohérent.)

Le verrouillage en écriture est requis pour empêcher les modifications simultanées. peut entraîner le tableau contenant des données incohérentes ou des modifications étant perdues.

+1

Ce n'est pas 100% précis. L'atomicité de la définition de la référence n'est pas suffisante pour garantir la cohérence, et les règles du modèle de mémoire Java résolvent ce problème. Des écritures hors service et un réordonnancement des instructions peuvent se produire, puis un thread peut recevoir une référence pointant vers un objet incohérent. Cela se produit également avec le modèle de double vérification des chèques (voir http://www.ibm.com/developerworks/java/library/j-dcl.html) –

+3

Ce n'est pas la même chose. lire/écrire à un volatile est consisdered ce que le JMM appelle une «action synchronisée» et définit une barrière sur ce qui peut être réorganisé. Voir http://java.sun.com/docs/books/jls/third_edition/html/memory.html – mdma

+1

@Eyal Schneider: Bienvenue en 2004 (voir http://www.ibm.com/developerworks/library/j- jtp03304 /). Lire la section intitulée "Nouvelles garanties pour volatiles" –

Questions connexes