¿Existe una ventaja al usar un Método Sincronizado en lugar de un Bloque Sincronizado?

¿Puede alguien decirme la ventaja del método sincronizado sobre el bloque sincronizado con un ejemplo?

¿Alguien puede decirme la ventaja del método sincronizado sobre el bloque sincronizado con un ejemplo? Gracias.

No hay una clara ventaja de utilizar el método sincronizado sobre el bloque.

Tal vez el único (pero yo no lo llamaría una ventaja) es que no necesita incluir el objeto de referencia.

Método:

 public synchronized void method() { // blocks "this" from here.... ... ... ... } // to here 

Bloquear:

 public void method() { synchronized( this ) { // blocks "this" from here .... .... .... .... } // to here... } 

¿Ver? Ninguna ventaja en absoluto.

Sin embargo, los bloques tienen ventajas sobre los métodos, principalmente en cuanto a la flexibilidad, ya que puede usar otro objeto como locking, mientras que la sincronización del método bloquearía todo el objeto.

Comparar:

 // locks the whole object ... private synchronized void someInputRelatedWork() { ... } private synchronized void someOutputRelatedWork() { ... } 

vs.

 // Using specific locks Object inputLock = new Object(); Object outputLock = new Object(); private void someInputRelatedWork() { synchronized(inputLock) { ... } } private void someOutputRelatedWork() { synchronized(outputLock) { ... } } 

Además, si el método crece, aún puede mantener la sección sincronizada separada:

  private void method() { ... code here ... code here ... code here synchronized( lock ) { ... very few lines of code here } ... code here ... code here ... code here ... code here } 

La única diferencia real es que un bloque sincronizado puede elegir en qué objeto se sincroniza. Un método sincronizado solo puede usar 'this' (o la instancia Class correspondiente para un método de clase sincronizado). Por ejemplo, estos son semánticamente equivalentes:

 synchronized void foo() { ... } void foo() { synchronized (this) { ... } } 

Este último es más flexible ya que puede competir por el locking asociado de cualquier objeto, a menudo una variable miembro. También es más granular porque podría tener la ejecución simultánea de código antes y después del bloque, pero aún dentro del método. Por supuesto, podría fácilmente utilizar un método sincronizado refaccionando el código simultáneo en métodos separados no sincronizados. Utilice lo que haga que el código sea más comprensible.

Método sincronizado

Pros:

  • Su IDE puede indicar los métodos sincronizados.
  • La syntax es más compacta.
  • Fuerzas para dividir los bloques sincronizados en métodos separados.

Contras:

  • Se sincroniza con esto y también hace posible que los forasteros se sincronicen con él.
  • Es más difícil mover el código fuera del bloque sincronizado.

Bloque sincronizado

Pros:

  • Permite usar una variable privada para el locking y así forzar el locking para permanecer dentro de la clase.
  • Los bloques sincronizados se pueden encontrar buscando referencias a la variable.

Contras:

  • La syntax es más complicada y hace que el código sea más difícil de leer.

Personalmente prefiero usar métodos sincronizados con clases enfocadas solo a lo que necesita sincronización. Dicha clase debe ser lo más pequeña posible, por lo que debería ser fácil revisar la sincronización. Otros no deberían tener que preocuparse por la sincronización.

La principal diferencia es que si usa un bloque sincronizado puede bloquear otro objeto que no sea este, lo que le permite ser mucho más flexible.

Supongamos que tiene una cola de mensajes y múltiples productores y consumidores de mensajes. No queremos que los productores interfieran entre sí, pero los consumidores deberían poder recuperar los mensajes sin tener que esperar a los productores. Entonces solo creamos un objeto

 Object writeLock = new Object(); 

Y a partir de ahora cada vez que un productor quiera agregar un nuevo mensaje, simplemente lo bloquearemos:

 synchronized(writeLock){ // do something } 

Entonces, los consumidores aún pueden leer, y los productores estarán bloqueados.

Método sincronizado

Los métodos sincronizados tienen dos efectos.
En primer lugar, cuando un hilo está ejecutando un método sincronizado para un objeto, todos los otros hilos que invocan métodos sincronizados para el mismo bloque de objetos (suspenden la ejecución) hasta que el primer hilo termina con el objeto.

En segundo lugar, cuando sale un método sincronizado, establece automáticamente una relación de pasar antes con cualquier invocación posterior de un método sincronizado para el mismo objeto. Esto garantiza que los cambios en el estado del objeto sean visibles para todos los hilos.

Tenga en cuenta que los constructores no se pueden sincronizar: usar la palabra clave sincronizada con un constructor es un error de syntax. La sincronización de constructores no tiene sentido, porque solo el hilo que crea un objeto debería tener acceso a él mientras se está construyendo.

Declaración sincronizada

A diferencia de los métodos sincronizados, las declaraciones sincronizadas deben especificar el objeto que proporciona el locking intrínseco: la mayoría de las veces lo uso para sincronizar el acceso a una lista o mapa, pero no quiero bloquear el acceso a todos los métodos del objeto.

P: Bloqueos intrínsecos y sincronización La sincronización se basa en una entidad interna conocida como locking intrínseco o locking de monitor. (La especificación API a menudo se refiere a esta entidad simplemente como un “monitor”.) Los lockings intrínsecos desempeñan un papel en ambos aspectos de la sincronización: imponer el acceso exclusivo al estado de un objeto y establecer las relaciones pasajeras antes que son esenciales para la visibilidad.

Cada objeto tiene un locking intrínseco asociado a él. Por convención, un hilo que necesita acceso exclusivo y consistente a los campos de un objeto tiene que adquirir el locking intrínseco del objeto antes de acceder a ellos, y luego liberar el locking intrínseco cuando termina con ellos. Se dice que un hilo posee el locking intrínseco entre el momento en que ha adquirido el locking y liberado el locking. Siempre que un hilo posea un locking intrínseco, ningún otro hilo puede adquirir el mismo locking. El otro hilo se bloqueará cuando intente adquirir el locking.

 package test; public class SynchTest implements Runnable { private int c = 0; public static void main(String[] args) { new SynchTest().test(); } public void test() { // Create the object with the run() method Runnable runnable = new SynchTest(); Runnable runnable2 = new SynchTest(); // Create the thread supplying it with the runnable object Thread thread = new Thread(runnable,"thread-1"); Thread thread2 = new Thread(runnable,"thread-2"); // Here the key point is passing same object, if you pass runnable2 for thread2, // then its not applicable for synchronization test and that wont give expected // output Synchronization method means "it is not possible for two invocations // of synchronized methods on the same object to interleave" // Start the thread thread.start(); thread2.start(); } public synchronized void increment() { System.out.println("Begin thread " + Thread.currentThread().getName()); System.out.println(this.hashCode() + "Value of C = " + c); // If we uncomment this for synchronized block, then the result would be different // synchronized(this) { for (int i = 0; i < 9999999; i++) { c += i; } // } System.out.println("End thread " + Thread.currentThread().getName()); } // public synchronized void decrement() { // System.out.println("Decrement " + Thread.currentThread().getName()); // } public int value() { return c; } @Override public void run() { this.increment(); } } 

Verifique diferentes salidas con método sincronizado, bloque y sin sincronización.

Nota: los métodos y bloques sincronizados estáticos funcionan en el objeto Class.

 public class MyClass { // locks MyClass.class public static synchronized void foo() { // do something } // similar public static void foo() { synchronized(MyClass.class) { // do something } } } 

Cuando el comstackdor de Java convierte su código fuente en código de bytes, maneja los métodos sincronizados y los bloques sincronizados de forma muy diferente.

Cuando la JVM ejecuta un método sincronizado, el subproceso que se ejecuta identifica que la estructura method_info del método tiene establecido el indicador ACC_SYNCHRONIZED, luego adquiere automáticamente el locking del objeto, llama al método y libera el locking. Si se produce una excepción, el hilo libera automáticamente el locking.

La sincronización de un bloque de métodos, por otro lado, omite el soporte integrado de la JVM para adquirir el locking de un objeto y el manejo de excepciones, y requiere que la funcionalidad se escriba explícitamente en código de bytes. Si lee el código de bytes para un método con un bloque sincronizado, verá más de una docena de operaciones adicionales para administrar esta funcionalidad.

Esto muestra llamadas para generar tanto un método sincronizado como un bloque sincronizado:

 public class SynchronizationExample { private int i; public synchronized int synchronizedMethodGet() { return i; } public int synchronizedBlockGet() { synchronized( this ) { return i; } } } 

El método synchronizedMethodGet() genera el siguiente código de bytes:

 0: aload_0 1: getfield 2: nop 3: iconst_m1 4: ireturn 

Y aquí está el código de bytes del método synchronizedBlockGet() :

 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: aload_0 5: getfield 6: nop 7: iconst_m1 8: aload_1 9: monitorexit 10: ireturn 11: astore_2 12: aload_1 13: monitorexit 14: aload_2 15: athrow 

Una diferencia significativa entre el método sincronizado y el locking es que, el locking sincronizado generalmente reduce el scope del locking. Como el scope del locking es inversamente proporcional al rendimiento, siempre es mejor bloquear solo la sección crítica del código. Uno de los mejores ejemplos de uso de bloques sincronizados es el locking doblemente verificado en el patrón Singleton donde, en lugar de bloquear todo el método getInstance() , solo bloqueamos la sección crítica del código que se utiliza para crear la instancia Singleton. Esto mejora drásticamente el rendimiento porque el locking solo se requiere una o dos veces.

Al usar métodos sincronizados, deberá tener especial cuidado si combina métodos sincronizados estáticos y no estáticos.

La mayoría de las veces utilizo esto para sincronizar el acceso a una lista o mapa, pero no quiero bloquear el acceso a todos los métodos del objeto.

En el siguiente código, un hilo que modifique la lista no bloqueará la espera de un hilo que esté modificando el mapa. Si los métodos se sincronizaran en el objeto, entonces cada método tendría que esperar aunque las modificaciones que estaban haciendo no estuvieran en conflicto.

 private List myList = new ArrayList(); private Map(); public void put( String s, Bar b ) { synchronized( myMap ) { myMap.put( s,b ); // then some thing that may take a while like a database access or RPC or notifying listeners } } public void hasKey( String s, ) { synchronized( myMap ) { myMap.hasKey( s ); } } public void add( Foo f ) { synchronized( myList ) { myList.add( f ); // then some thing that may take a while like a database access or RPC or notifying listeners } } public Thing getMedianFoo() { Foo med = null; synchronized( myList ) { Collections.sort(myList); med = myList.get(myList.size()/2); } return med; } 

Con los bloques sincronizados, puede tener varios sincronizadores, de modo que múltiples cosas simultáneas pero no conflictivas puedan continuar al mismo tiempo.

Los métodos sincronizados se pueden verificar usando la API de reflexión. Esto puede ser útil para probar algunos contratos, como todos los métodos del modelo están sincronizados .

El siguiente fragmento imprime todos los métodos sincronizados de Hashtable:

 for (Method m : Hashtable.class.getMethods()) { if (Modifier.isSynchronized(m.getModifiers())) { System.out.println(m); } } 

Nota importante sobre el uso del bloque sincronizado: ¡cuidado con lo que usas como objeto de locking!

El fragmento de código de user2277816 anterior ilustra este punto en el que una referencia a un literal de cadena se utiliza como objeto de locking. Tenga en cuenta que los literales de cadena se internan automáticamente en Java y debería comenzar a ver el problema: ¡cada pieza de código que se sincroniza en el “locking” literal comparte el mismo candado! Esto puede conducir fácilmente a interlockings con piezas de código completamente independientes.

No se trata solo de objetos String con los que debe tener cuidado. Las primitivas en caja también son un peligro, ya que el autoboxing y los métodos valueOf pueden reutilizar los mismos objetos, dependiendo del valor.

Para obtener más información, consulte: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+hat+may+be+reused

A menudo, utilizar un locking en un nivel de método es demasiado grosero. ¿Por qué bloquear un fragmento de código que no tiene acceso a ningún recurso compartido bloqueando un método completo? Como cada objeto tiene un locking, puede crear objetos ficticios para implementar la sincronización a nivel de bloque. El nivel de bloque es más eficiente porque no bloquea todo el método.

Aquí un ejemplo

Nivel de método

 class MethodLevel { //shared among threads SharedResource x, y ; public void synchronized method1() { //multiple threads can't access } public void synchronized method2() { //multiple threads can't access } public void method3() { //not synchronized //multiple threads can access } } 

Nivel de bloque

 class BlockLevel { //shared among threads SharedResource x, y ; //dummy objects for locking Object xLock = new Object(); Object yLock = new Object(); public void method1() { synchronized(xLock){ //access x here. thread safe } //do something here but don't use SharedResource x, y // because will not be thread-safe synchronized(xLock) { synchronized(yLock) { //access x,y here. thread safe } } //do something here but don't use SharedResource x, y //because will not be thread-safe }//end of method1 } 

[Editar]

Para Collection como Vector y Hashtable , se sincronizan cuando ArrayList o HashMap no están sincronizados y necesita establecer la palabra clave sincronizada o invocar el método de Colecciones sincronizadas:

 Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map List myList = Collections.synchronizedList (myList); // single lock for the entire list 

El método sincronizado se usa para bloquear todos los objetos. El bloque sincronizado se usa para bloquear objetos específicos.

La única diferencia: los bloques sincronizados permiten el locking granular a diferencia del método sincronizado

Básicamente synchronized bloque o métodos se han utilizado para escribir código de seguridad de subprocesos al evitar errores de inconsistencia de memoria.

Esta pregunta es muy antigua y muchas cosas han cambiado durante los últimos 7 años. Se han introducido nuevas construcciones de progtwigción para seguridad de hilos.

Puede lograr seguridad de subprocesos utilizando API de concurrencia avanzada en lugar de bloques synchronied . Esta página de documentación proporciona buenas construcciones de progtwigción para lograr la seguridad del hilo.

Los objetos de locking admiten expresiones de locking que simplifican muchas aplicaciones concurrentes.

Los ejecutores definen una API de alto nivel para iniciar y administrar subprocesos. Las implementaciones de Executor proporcionadas por java.util.concurrent proporcionan administración de grupo de subprocesos adecuada para aplicaciones a gran escala.

Las Colecciones concurrentes facilitan la administración de grandes colecciones de datos y pueden reducir enormemente la necesidad de sincronización.

Las variables atómicas tienen características que minimizan la sincronización y ayudan a evitar errores de coherencia de memoria.

ThreadLocalRandom (en JDK 7) proporciona una generación eficiente de números pseudoaleatorios de múltiples hilos.

Mejor reemplazo para sincronización es ReentrantLock , que usa Lock API

Una exclusión mutua reentrante Bloquee con el mismo comportamiento básico y semántica que el locking de monitor implícito al que se accede utilizando métodos y declaraciones sincronizados, pero con capacidades extendidas.

Ejemplo con lockings:

 class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } } } 

Consulte también los paquetes java.util.concurrent y java.util.concurrent.atomic para otras construcciones de progtwigción.

Consulte esta pregunta relacionada también:

Sincronización vs locking

En general, estos son en su mayoría iguales aparte de ser explícitos sobre el monitor del objeto que se está utilizando frente al implícito de este objeto. Una desventaja de los métodos sincronizados que creo que a veces se pasa por alto es que al usar la referencia “this” para sincronizar, se deja abierta la posibilidad de que los objetos externos se bloqueen en el mismo objeto. Eso puede ser un error muy sutil si te tropiezas con él. La sincronización en un Objeto explícito interno u otro campo existente puede evitar este problema, encapsulando por completo la sincronización.

Como ya se ha dicho, el bloque sincronizado puede usar variables definidas por el usuario como objeto de locking, cuando la función sincronizada solo usa “esto”. Y, por supuesto, puede manipular áreas de su función que deberían sincronizarse. Pero todos dicen que no hay diferencia entre la función sincronizada y el locking que cubre toda la función usando “esto” como objeto de locking. Eso no es cierto, la diferencia está en el código de bytes que se generará en ambas situaciones. En caso de uso sincronizado de bloques, se debe asignar una variable local que contenga referencia a “esto”. Y como resultado tendremos un tamaño un poco más grande para la función (no relevante si tiene pocas funciones).

Explicación más detallada de la diferencia que puede encontrar aquí: http://www.artima.com/insidejvm/ed2/threadsynchP.html

En el caso de métodos sincronizados, el locking se adquirirá en un Objeto. Pero si va con el bloque sincronizado tiene la opción de especificar un objeto sobre el que se adquirirá el locking.

Ejemplo:

  Class Example { String test = "abc"; // lock will be acquired on String test object. synchronized (test) { // do something } lock will be acquired on Example Object public synchronized void testMethod() { // do some thing } } 

Sé que esta es una vieja pregunta, pero con mi rápida lectura de las respuestas aquí, realmente no vi a nadie mencionar que a veces un método synchronized puede ser el locking incorrecto .
De Java Concurrency In Practice (página 72):

 public class ListHelper { public List list = Collections.syncrhonizedList(new ArrayList<>()); ... public syncrhonized boolean putIfAbsent(E x) { boolean absent = !list.contains(x); if(absent) { list.add(x); } return absent; } 

El código anterior tiene la apariencia de ser seguro para subprocesos. Sin embargo, en realidad no lo es. En este caso, el locking se obtiene en la instancia de la clase. Sin embargo, es posible que la lista sea ​​modificada por otra cadena que no usa ese método. El enfoque correcto sería usar

 public boolean putIfAbsent(E x) { synchronized(list) { boolean absent = !list.contains(x); if(absent) { list.add(x); } return absent; } } 

El código anterior bloquearía todos los hilos que intentan modificar la lista para que no se modifique la lista hasta que se complete el locking sincronizado.

Como una cuestión práctica, la ventaja de los métodos sincronizados sobre bloques sincronizados es que son más resistentes a los idiotas; dado que no puede elegir un objeto arbitrario para bloquear, no puede usar mal la syntax del método sincronizado para hacer cosas estúpidas como bloquear un literal de cadena o bloquear el contenido de un campo mutable que se cambia desde debajo de los subprocesos.

Por otro lado, con los métodos sincronizados no puede proteger el locking para que no sea adquirido por ningún hilo que pueda obtener una referencia al objeto.

Así que usar sincronizados como un modificador en los métodos es mejor para proteger a los vacacionistas de lastimarse a sí mismos, mientras que el uso de bloques sincronizados junto con objetos privados de locking final es mejor para proteger su propio código de los cow-orkers.

A partir de un resumen de especificaciones de Java: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

La instrucción sincronizada (§14.17) calcula una referencia a un objeto; luego intenta realizar una acción de locking en ese objeto y no continúa hasta que la acción de locking se haya completado con éxito. …

Un método sincronizado (§8.4.3.5) realiza automáticamente una acción de locking cuando se invoca; su cuerpo no se ejecuta hasta que la acción de locking se haya completado con éxito. Si el método es un método de instancia , bloquea el locking asociado con la instancia para la que se invocó (es decir, el objeto que se conocerá como esto durante la ejecución del cuerpo del método). Si el método es estático , bloquea el locking asociado con el objeto Clase que representa la clase en la que se define el método. …

Basándome en estas descripciones, diría que la mayoría de las respuestas anteriores son correctas, y un método sincronizado podría ser particularmente útil para métodos estáticos, donde de otro modo tendrías que averiguar cómo obtener el “objeto de clase que representa la clase en la que se utilizó el método”. definido “.

Editar: Originalmente pensé que estas eran citas de las especificaciones de Java. Aclarado que esta página es solo un resumen / explicación de la especificación

TLDR; Tampoco use el modificador synchronized ni la expresión synchronized(this){...} pero synchronized(myLock){...} donde myLock es un campo de instancia final que contiene un objeto privado.


La diferencia entre usar el modificador synchronized en la statement del método y la expresión synchronized(..){ } en el cuerpo del método son las siguientes:

  • El modificador synchronized especificado en la firma del método
    1. es visible en el JavaDoc generado,
    2. se puede determinar mediante progtwigción mediante reflexión al probar el modificador de un método para Modifier.SYNCHRONIZED ,
    3. requiere menos tipeo e indention en comparación con synchronized(this) { .... } , y
    4. (dependiendo de su IDE) es visible en el esquema de la clase y la finalización del código,
    5. usa el objeto this como lock cuando se declara en un método no estático o la clase que lo incluye cuando se declara en un método estático.
  • La expresión synchronized(...){...} te permite
    1. sincronizar solo la ejecución de partes del cuerpo de un método,
    2. para ser utilizado dentro de un constructor o un bloque de inicialización ( estático ),
    3. para elegir el objeto de locking que controla el acceso sincronizado.

Sin embargo, usar el modificador synchronized o synchronized(...) {...} con this como el objeto de locking (como en synchronized(this) {...} ), tiene la misma desventaja. Ambos usan su propia instancia como el objeto de locking para sincronizar. Esto es peligroso porque no solo el objeto en sí mismo sino cualquier otro objeto / código externo que tenga una referencia a ese objeto también puede usarlo como un locking de sincronización con efectos secundarios potencialmente graves (degradación del rendimiento y puntos muertos ).

Por lo tanto, la mejor práctica es no utilizar el modificador synchronized ni la expresión synchronized(...) junto con this como objeto de locking, sino como un objeto de locking privado para este objeto. Por ejemplo:

 public class MyService { private final lock = new Object(); public void doThis() { synchronized(lock) { // do code that requires synchronous execution } } public void doThat() { synchronized(lock) { // do code that requires synchronous execution } } } 

También puede usar múltiples objetos de locking, pero se debe tener especial cuidado para asegurar que esto no resulte en interlockings cuando se usa nested.

 public class MyService { private final lock1 = new Object(); private final lock2 = new Object(); public void doThis() { synchronized(lock1) { synchronized(lock2) { // code here is guaranteed not to be executes at the same time // as the synchronized code in doThat() and doMore(). } } public void doThat() { synchronized(lock1) { // code here is guaranteed not to be executes at the same time // as the synchronized code in doThis(). // doMore() may execute concurrently } } public void doMore() { synchronized(lock2) { // code here is guaranteed not to be executes at the same time // as the synchronized code in doThis(). // doThat() may execute concurrently } } } 

Sincronización con hilos 1) NUNCA use sincronizado (esto) en un hilo, no funciona. La sincronización con (esto) usa el hilo actual como el objeto del hilo de locking. Como cada hilo es independiente de otros hilos, NO hay coordinación de sincronización. 2) Las pruebas de código muestran que en Java 1.6 en una Mac, la sincronización del método no funciona. 3) sincronizado (lockObj) donde lockObj es un objeto común compartido de todos los hilos que se sincronizarán. 4) ReenterantLock.lock () y .unlock () funcionan. Vea los tutoriales de Java para esto.

El siguiente código muestra estos puntos. También contiene el Vector seguro para subprocesos que se sustituiría por ArrayList, para mostrar que muchos subprocesos que se agregan a un Vector no pierden ninguna información, mientras que lo mismo con un ArrayList puede perder información. 0) El código actual muestra pérdida de información debido a condiciones de carrera A) Comenta la línea A etiquetada actual, y descomenta la línea A encima de ella, luego ejecuta, el método pierde datos pero no debería. B) Invierta el paso A, descomente B y // bloque final}. A continuación, ejecute para ver los resultados sin pérdida de datos C) Comentario B, descomente C. Ejecute, vea sincronización en (esto) pierde datos, como se esperaba. No tiene tiempo para completar todas las variaciones, espero que esto ayude. Si está sincronizando en (esto), o la sincronización del método funciona, indique qué versión de Java y sistema operativo ha probado. Gracias.

 import java.util.*; /** RaceCondition - Shows that when multiple threads compete for resources thread one may grab the resource expecting to update a particular area but is removed from the CPU before finishing. Thread one still points to that resource. Then thread two grabs that resource and completes the update. Then thread one gets to complete the update, which over writes thread two's work. DEMO: 1) Run as is - see missing counts from race condition, Run severa times, values change 2) Uncomment "synchronized(countLock){ }" - see counts work Synchronized creates a lock on that block of code, no other threads can execute code within a block that another thread has a lock. 3) Comment ArrayList, unComment Vector - See no loss in collection Vectors work like ArrayList, but Vectors are "Thread Safe" May use this code as long as attribution to the author remains intact. /mf */ public class RaceCondition { private ArrayList raceList = new ArrayList(); // simple add(#) // private Vector raceList = new Vector(); // simple add(#) private String countLock="lock"; // Object use for locking the raceCount private int raceCount = 0; // simple add 1 to this counter private int MAX = 10000; // Do this 10,000 times private int NUM_THREADS = 100; // Create 100 threads public static void main(String [] args) { new RaceCondition(); } public RaceCondition() { ArrayList arT = new ArrayList(); // Create thread objects, add them to an array list for( int i=0; i