Seguridad de subprocesos al realizar iteraciones a través de un ArrayList utilizando foreach
Tengo un ArrayList
que está siendo instanciado y poblado en el hilo de fondo (lo uso para almacenar los datos del Cursor
). Al mismo tiempo se puede acceder en el hilo principal e iterado mediante el uso de foreach. Así que esto obviamente puede resultar en lanzar una excepción.
Mi pregunta es ¿cuál es la mejor práctica para hacer que este campo de clase sea seguro para el hilo sin copiarlo cada vez o usar banderas?
- Android verificar el estado WIFI (desconectado o el usuario ha cambiado WIFI) ¿Cómo FLAG?
- La clase interna puede acceder pero no actualizar los valores - AsyncTask
- ¿Cuándo debo usar cada uno de los diferentes tipos de mensajería de Android?
- ¿Puedo tener un ejemplo de mostrar un brindis usando runOnUiThread.
- NullPointerException: lock == null
class SomeClass { private final Context mContext; private List<String> mList = null; SomeClass(Context context) { mContext = context; } public void populateList() { new Thread(new Runnable() { @Override public void run() { mList = new ArrayList<>(); Cursor cursor = mContext.getContentResolver().query( DataProvider.CONTENT_URI, null, null, null, null); try { while (cursor.moveToNext()) { mList.add(cursor.getString(cursor.getColumnIndex(DataProvider.NAME))); } } catch (Exception e) { Log.e("Error", e.getMessage(), e); } finally { if (cursor != null) { cursor.close(); } } } }).start(); } public boolean searchList(String query) { // Invoked on the main thread if (mList != null) { for (String name : mList) { if (name.equals(query) { return true; } } } return false; } }
- ¿Cómo hago la aplicación de Android que hace algo cada X segundo
- Compartir el contexto GLES20 y texturas entre GLSurfaceViews diferentes?
- Facebook y GoogleAnalytics está causando la señal fatal 11 (SIGSEGV)
- Leer datos de sqlite cuando se está ejecutando una transacción (Android)
- Llamar stopSelf () en servicio mientras el subproceso está en ejecución
- Android "Sólo el subproceso original que creó una jerarquía de vistas puede tocar sus vistas." Error en Fragmento
- ¿Cómo probar mejor el código Looper y Handler de la unidad en Android?
- Handler.post (runnable) no ejecuta siempre el método run en android
En general, es una muy mala idea operar simultáneamente en una estructura de datos que no es thread-safe. No tiene ninguna garantía de que la implementación no cambiará en el futuro, lo que puede afectar gravemente el comportamiento en tiempo de ejecución de la aplicación, es decir, java.util.HashMap produce bucles infinitos al ser modificado simultáneamente.
Para acceder a una lista simultáneamente, Java proporciona java.util.concurrent.CopyOnWriteArrayList
. El uso de esta implementación resolverá su problema de varias maneras:
- Es thread-safe, permitiendo modificaciones simultáneas
- La iteración sobre instantáneas de la lista no se ve afectada por las operaciones de adición concurrentes, permitiendo simultáneamente agregar e iterar
- Es más rápido que la sincronización
Alternativamente, si no se utiliza una copia de la matriz interna es un requisito estricto (que no puedo imaginar en su caso, la matriz es bastante pequeña, ya que sólo contiene referencias de objetos, que se pueden copiar en la memoria con bastante eficiencia), puede Sincronizar el acceso en el mapa. Pero eso requeriría que el mapa se inicializara correctamente, de lo contrario su código puede lanzar una NullPointerException
porque el orden de ejecución de subprocesos no está garantizado (asume que el populateList()
se inicia antes, por lo que la lista se inicializa.Cuando se utiliza un bloque sincronizado , Elija el bloque protegido sabiamente.Si tiene todo el contenido del método run()
en un bloque sincronizado, el hilo del lector tiene que esperar hasta que los resultados del cursor se procesen – lo que puede tomar un tiempo – por lo que realmente suelto Toda concurrencia.
Si usted decide ir para el bloque sincronizado, haría los cambios siguientes (y no lo reclamo, ellos son perfectamente correctos):
Inicialice el campo de lista para poder sincronizar el acceso en él:
private List<String> mList = new ArrayList<>(); //initialize the field
Sincronizar la operación de modificación (add). No lea los datos del cursor dentro del bloque de sincronización porque si es una operación de baja latencia, el mList no se pudo leer durante esa operación, bloqueando todos los otros hilos durante bastante tiempo.
//mList = new ArrayList<>(); remove that line in your code String data = cursor.getString(cursor.getColumnIndex(DataProvider.NAME)); //do this before synchronized block! synchronized(mList){ mList.add(data); }
La iteración de lectura debe estar dentro del bloque de sincronización, por lo que no se agrega ningún elemento, mientras se itera sobre:
synchronized(mList){ for (String name : mList) { if (name.equals(query) { return true; } } }
Así que cuando dos hilos funcionan en la lista, un hilo puede agregar un solo elemento o iterar sobre la lista completa a la vez. No tiene ejecución paralela en estas partes del código.
Respecto a las versiones sincronizadas de una lista (es decir Vector
, Collections.synchronizedList()
). Éstos pueden ser menos eficientes porque con la sincronización realmente pierdes la ejecución paralela ya que sólo un hilo puede ejecutar los bloques protegidos a la vez. Además, pueden seguir siendo propensos a ConcurrentModificationException
, que puede incluso ocurrir en un solo hilo. Se lanza, si la estructura de datos se modifica entre la creación de iteradores y el iterador debe continuar. Así que esas estructuras de datos no resolverán su problema.
No recomiendo la sincronización manual también, porque el riesgo de simplemente hacerla mal es demasiado alto (sincronización en el monitory incorrecto o diferente, bloques de sincronización demasiado grandes, …)
TL, DR
Utilice java.util.concurrent.CopyOnWriteArrayList
Utilice Collections.synchronizedList(new ArrayList<T>());
Ex:
Collections.synchronizedList(mList);
Java bloque sincronizado http://www.tutorialspoint.com/java/java_thread_synchronization.htm
class SomeClass { private final Context mContext; private List<String> mList = null; SomeClass(Context context) { mContext = context; } public void populateList() { new Thread(new Runnable() { @Override public void run() { synchronized(SomeClass.this){ mList = new ArrayList<>(); Cursor cursor = mContext.getContentResolver().query( DataProvider.CONTENT_URI, null, null, null, null); try { while (cursor.moveToNext()) { mList.add(cursor.getString(cursor.getColumnIndex(DataProvider.NAME))); } } catch (Exception e) { Log.e("Error", e.getMessage(), e); } finally { if (cursor != null) { cursor.close(); } } } } }).start(); } public boolean searchList(String query) { // Invoked on the main thread synchronized(SomeClass.this){ if (mList != null) { for (String name : mList) { if (name.equals(query) { return true; } } } return false; } } }
Puede utilizar un Vector
que sea el equivalente a ArrayList
de thread-safe.
EDIT: Gracias al comentario de Fildor , ahora sé que esto no evita que ConcurrentModificationException
se lance con varios subprocesos:
Sólo se sincronizarán las llamadas individuales. Por lo tanto, una adición no se puede llamar mientras otro subproceso está llamando add, por ejemplo. Pero alterar la lista hará que el CME se lance mientras itera en otro hilo. Puede leer los documentos de iterador sobre ese tema.
También interesante:
- ¿Por qué la clase Java Vector se considera obsoleta o obsoleta?
- Vector vs Collections.synchronizedList (ArrayList)
Larga historia corta: NO utilice Vector
.