0

De l'CopyOnWriteArrayList.java, la méthode add est la suivante:Pourquoi CopyOnWriteArrayList copys lors de l'écriture?

public boolean add(E e) { 
    final ReentrantLock lock = this.lock; 
    lock.lock(); 
    try { 
     Object[] elements = getArray(); 
     int len = elements.length; 
     Object[] newElements = Arrays.copyOf(elements, len + 1); 
     newElements[len] = e; 
     setArray(newElements); 
     return true; 
    } finally { 
     lock.unlock(); 
    } 
    } 

Il est pas difficile de comprendre que ajouter opération devrait bloquer, ce qui me confond est qu'il copie les anciennes données à nouveau tableau et d'abandonner le précédent . quant à lui obtenir la méthode est la suivante:

public E get(int index) { 
     return (E)(getArray()[index]); 
    } 

sans verrou dans la méthode get. Je trouve quelques explications, certains disent que copier dans un nouveau tableau peut éviter d'ajouter et de faire fonctionner la méthode sur le même tableau. Mon problème est pourquoi deux threads ne peuvent pas lire et écrire en même temps?

+3

http://stackoverflow.com/questions/17853112/in-what-situations-is-the-copyonwritearraylist-suitable/17853225#17853225 –

Répondre

2

Si vous venez de regarder en haut de la classe CopyOnWriteArrayList environ array déclaration variablle référence il ya la réponse à votre question.

private volatile transient Object[] array; // this is volatile 

return (E)(getArray()[index]); 

qui retourne le dernier exemplaire de array[index] donc c'est threadsafe

final Object[] getArray() { 
     return array; 
    } 

getArray est de retour référence à array.

+0

Je pense que je comprends, merci. Une autre chose, si j'ai défini volatil int i = 1; thread A lire i, fil B i ++; quel genre de mauvais résultat pourrait se produire? –

+0

Si vous avez marqué la variable i comme volatile, alors il n'y aura pas de mauvais résultat car volatile assure la dernière copie de i – amicngh

+1

Notez que le marquage d'une référence volatile ne rend pas les opérations atomiques. Avec 'int volatile = 1;', 'i ++' est toujours une opération non-atomique * lecture-modification-écriture *, et l'exécuter à partir de plusieurs threads peut entraîner des mises à jour perdues. – bowmore

0

En fait, la raison pour laquelle le chemin d'écriture se verrouille n'est pas parce qu'il doit fournir une sécurité de thread en considérant le chemin de lecture, mais parce qu'il veut sérialiser les rédacteurs. Comme la technique de copie sur écriture remplace la référence volatile, il est généralement préférable de sérialiser cette opération.

La clé de cette idée est que les écritures sont accomplies en copiant la valeur existante, en la modifiant et en remplaçant la référence. Il s'ensuit également qu'une fois réglé, l'objet pointé par la référence est toujours en lecture seule (c'est-à-dire qu'aucune mutation n'est effectuée directement sur l'objet référencé par la référence). Par conséquent, les lecteurs peuvent y accéder en toute sécurité sans synchronisation.

La lecture et l'écriture peuvent se produire simultanément. Cependant, l'implication est que les lectures verront l'état bientôt-être-périmé jusqu'à ce que l'ensemble de référence volatile soit fait.

0

Au moment de get(), si plusieurs threads tentent d'obtenir dans la liste, il n'y aura pas de problème. Parce qu'en raison de volatile tableau il lira toujours la dernière copie et retournera l'élément de tableau.

Mais

Pendant add() ou set() à chaque fois, ils ont créé un nouveau tableau pour éviter les problèmes d'exécution réciproques, c'est une façon de faire des objets thread-safe pour rendre l'immuable.

S'ils ont utilisé même objet tableau au cours ajouter ou mettre alors ils doivent faire traversal synchronized.or il peut jeter exception si un thread ajouter/supprimer des objets à la liste lors de la traversée

Comme par java doc

une variante de thread-safe de java.util.ArrayList dans lequel toutes les opérations mutatifs (ajouter, ensemble, etc.) sont mises en œuvre en faisant une nouvelle copie du tableau sous-jacent.

C'est habituellement trop coûteux, mais peut-être plus efficaces que les solutions de rechange lorsque les opérations de traversal sont beaucoup plus nombreuses mutations, et est utile lorsque vous ne pouvez pas ou ne voulez pas synchroniser traversals

Voir cette

package com.concurrent; 

import java.util.List; 
import java.util.concurrent.CopyOnWriteArrayList; 

public class CopyOnWriteArrayListTest { 

    /** 
    * @param args 
    */ 
    public static void main(String[] args) { 
     CopyOnWriteArrayList<Integer> list=new CopyOnWriteArrayList<>(); 


     Viewer viewer=new Viewer(); 
     viewer.setList(list);  
     Thread t1=new Thread(viewer); 

     Adder adder=new Adder(); 
     adder.setList(list); 

     Thread t=new Thread(adder); 
     t.start(); 
     t1.start(); 

    } 

    static class Adder implements Runnable{ 

     private List<Integer> list; 
     public void setList(List<Integer> list) { 
      this.list = list; 
     } 
     @Override 
     public void run() { 
      for(int i=0;i<100;i++){ 
       list.add(i); 
       System.out.println("Added-"+i); 
       try { 
        Thread.sleep(500); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
      } 

     } 

    } 

    static class Viewer implements Runnable{ 

     private List<Integer> list; 
     public void setList(List<Integer> list) { 
      this.list = list; 
     } 
     @Override 
     public void run() { 
      while (true) { 
       System.out.println("Length of list->"+list.size()); 
       for (Integer i : list) { 
        System.out.println("Reading-"+i); 
        try { 
         Thread.sleep(500); 
        } catch (InterruptedException e) { 
         // TODO Auto-generated catch block 
         e.printStackTrace(); 
        } 
       } 
      } 

     } 

    } 
} 
Questions connexes