2017-02-14 100 views
5

Czy można bezpiecznie utworzyć nowe wystąpienie ArrayList przez konstruktora ArrayList(Collection<? extends E> sourceCollection) bez żadnych dodatkowych synchronizacji, zakładając, że sourceCollection jest zsynchronizowany? A dokładniej, czy możemy polegać w tej sprawie na nowej liście, aby zawierała dokładnie te elementy, które były w kolekcji w czasie, gdy wywołano new ArrayList(sourceCollection)? Czy możemy polegać na nowej liście, która jest w stanie spójnym?ArrayList (Collection <? extends E> c) bezpieczne wątku?

Zadaję to pytanie, ponieważ widziałem przykłady w książkach na temat współbieżności sposobu ograniczania obiektów do zmiennych lokalnych na stosie wątku. W tych przykładach przekazywana jest metoda odwoływania się do obiektu współdzielonego, a wewnątrz metody kopia obiektu jest przechowywana w zmiennej lokalnej - wszystko to bez żadnej synchronizacji. Twierdzi się, że w ten sposób można osiągnąć bezpieczeństwo gwintów. Ogólny Przykładem może być:

public void someMethod(Collection<String> source) { 
    List<String> localList = new ArrayList<>(source); 
    ... 
} 
+1

Jest bezpieczny tylko wtedy, gdy źródło jest bezpieczne (np. Niezmienne, zsynchronizowane lub współbieżne). – shmosel

+0

Masz na myśli, nawet jeśli 'sourceCollection' jest * un * zsynchronizowany? –

+1

Nie, mam na myśli synchronizację. –

Odpowiedz

4

Podpowiedź: jak @John Bollinger słusznie wspomniano, zwłaszcza ArrayList realizacja nie jest objęte specyfikacją języka. Tak napisane poniżej jest prawdziwe dla implementacji Oracle java 8.


Tak, to jest bezpieczne, jeśli source jest zsynchronizowany zbiór, bo ArrayList konstruktor w tym przypadku wykorzystuje toArray() metodę zbierania źródłowego, który jest zsynchronizowany, jak również i tworzenia nowych kopii danych:

public ArrayList(Collection<? extends E> c) { 
    elementData = c.toArray(); 
    // ... 
} 
+0

W pewnym sensie. Ale konstruktor 'ArrayList' nie jest * udokumentowany *, aby zachowywać się w ten sposób, a jego udokumentowane zachowanie jest spójne z alternatywnymi implementacjami, które nie byłyby bezpieczne dla wątków. W tym sensie nie, proponowana operacja jest * nie * bezpieczna dla wątków. –

+0

W tym sensie możemy mówić o bardzo szerokim zakresie rzeczy. Ale generalnie zgadzam się z twoim punktem widzenia. – Andremoniy

+0

@JohnBollinger dodał twoją podpowiedź w mojej odpowiedzi – Andremoniy

0

Odpowiedź na konkretne pytanie jest odpowiedzią na pytanie - czy wątek źródłowy jest bezpieczny?

Najlepszym sposobem, aby spróbować zrozumieć, jak do niego dotarliśmy, możemy przejść do źródła.

Począwszy ArrayList

public ArrayList(Collection<? extends E> c) { 
    elementData = c.toArray(); 
     size = elementData.length; 
     // c.toArray might (incorrectly) not return Object[] (see 6260652) 
     if (elementData.getClass() != Object[].class) 
      elementData = Arrays.copyOf(elementData, size, Object[].class); 
    } 

nurkowanie w dokumentacji Collection.toArray

/** 
* Returns an array containing all of the elements in this collection. 
* If this collection makes any guarantees as to what order its elements 
* are returned by its iterator, this method must return the elements in 
* the same order. 
* 
* <p>The returned array will be "safe" in that no references to it are 
* maintained by this collection. (In other words, this method must 
* allocate a new array even if this collection is backed by an array). 
* The caller is thus free to modify the returned array. 
* 
* <p>This method acts as bridge between array-based and collection-based 
* APIs. 
* 
* @return an array containing all of the elements in this collection 
*/ 
Object[] toArray(); 

Powrót do ArrayList.toArray (zakładając zbiorów źródłowych typ wykonania jest ArrayList)

/** 
* Returns an array containing all of the elements in this list 
* in proper sequence (from first to last element). 
* 
* <p>The returned array will be "safe" in that no references to it are 
* maintained by this list. (In other words, this method must allocate 
* a new array). The caller is thus free to modify the returned array. 
* 
* <p>This method acts as bridge between array-based and collection-based 
* APIs. 
* 
* @return an array containing all of the elements in this list in 
*   proper sequence 
*/ 
public Object[] toArray() { 
    return Arrays.copyOf(elementData, size); 
} 

i wreszcie na Array.copyOf

/** 
* Copies the specified array, truncating or padding with nulls (if necessary) 
* so the copy has the specified length. For all indices that are 
* valid in both the original array and the copy, the two arrays will 
* contain identical values. For any indices that are valid in the 
* copy but not the original, the copy will contain <tt>null</tt>. 
* Such indices will exist if and only if the specified length 
* is greater than that of the original array. 
* The resulting array is of the class <tt>newType</tt>. 
* 
* @param <U> the class of the objects in the original array 
* @param <T> the class of the objects in the returned array 
* @param original the array to be copied 
* @param newLength the length of the copy to be returned 
* @param newType the class of the copy to be returned 
* @return a copy of the original array, truncated or padded with nulls 
*  to obtain the specified length 
* @throws NegativeArraySizeException if <tt>newLength</tt> is negative 
* @throws NullPointerException if <tt>original</tt> is null 
* @throws ArrayStoreException if an element copied from 
*  <tt>original</tt> is not of a runtime type that can be stored in 
*  an array of class <tt>newType</tt> 
* @since 1.6 
*/ 
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { 
    @SuppressWarnings("unchecked") 
    T[] copy = ((Object)newType == (Object)Object[].class) 
     ? (T[]) new Object[newLength] 
     : (T[]) Array.newInstance(newType.getComponentType(), newLength); 
    System.arraycopy(original, 0, copy, 0, 
        Math.min(original.length, newLength)); 
    return copy; 
} 

System.arraycopy jest metodą natywną. Wywołanie funkcji c.toArray u góry stosu wywołań używa System.arraycopy, która jest natywną metodą, która nie została udokumentowana jako bezpieczna dla wątków.

Przesunięcie w dół stosu, odpowiedź na pytanie jest odpowiedzią na pytanie - czy wątek źródłowy jest bezpieczny?

Jeśli używasz ArrayList jako kolekcji źródłowej, musisz zapewnić bezpieczeństwo wątków w swoim kodzie.

+2

Przechodzenie do źródła może być pomocne w * wyjaśnieniu i interpretacji * dokumentacji, ale rzadko jest polegać na czymkolwiek, co znajdziesz tam w jakikolwiek inny sposób. Jeśli jest to nieudokumentowane, może zmienić się bez ostrzeżenia. To może być dopuszczalne ryzyko, jeśli taka zmiana jest czymś, co na pewno szybko zauważysz, ale w żaden sposób nie mógłbym uzasadnić ryzyka, że ​​w wyniku takiej zmiany utracone zostanie bezpieczeństwo wątku mojego kodu. –

+0

Warto polegać na kodzie, niż polegać na dokumentacji. W tym przypadku, jeśli polegasz na kodzie i zauważysz, że nie jest on odpowiednio zsynchronizowany, kodujesz go i jeśli/kiedy zostałeś ponownie zaimplementowany jako bezpieczny dla wątków, nic nie tracisz. Robicie krok naprzód z mojej sugestii, aby przeczytać kod, który zarekomendował, ja tego nie robię. – Bhaskar

+0

Przykro mi, ale po prostu nie. Obserwacje szczegółów konkretnej implementacji, w tym jej kodu, mówią tylko o tej implementacji. Nie tylko głupie, ale i niebezpieczne jest poleganie na takich obserwacjach, aby odpowiedzieć na pytania bardziej ogólne niż te, takie jak PO. Z drugiej strony dokumentacja API określa oczekiwane zachowanie * każdej * prawidłowej implementacji. Opieranie się na dokumentacji zamiast implementacji jest jedną z realizacji dobrej praktyki programowania na interfejsach. –