Acceder y modificar una variable global desde distintos threads

Iniciado por fileteruso, 6 Febrero 2019, 20:20 PM

0 Miembros y 1 Visitante están viendo este tema.

fileteruso

Buenas,

estoy intentando que una variable global de tipo int sea modificada por dos tipos de procesos: uno que le suma 1 y otro que le resta 1. Para ello he creado dos clases que implementan Runnable: una llamada Incrementador y otra llamada Decrementador. El problema que tengo es que no se cómo hacer para que desde la clase principal que crea e inicia los procesos se comparta la variable para ir sumándole y restándole uno simultáneamente un número de veces concretas. He intentado pasarla cuando instancio ambas clases en la principal antes de iniciar los procesos pero en el momento que paso por parámetro la variable que se modifica es la propia de las clases Incrementador y Decrementador.

(En el código de la clase principal de a continuación solo ejecuto el proceso del incrementador para comprobar si le suma 1)

Código clase principal:

public class Principal {
public static volatile int x = 0;

public static void main(String[] args) {
Incrementador inc = new Incrementador(x);
Thread hilo = new Thread(inc);
hilo.start();
System.out.println(x);
}
}


Código del incrementador:

public class Incrementador implements Runnable {
private int x;

public Incrementador(int x) {
this.x = x;
}

public void run() {
x=x+1;
}
}


Código del Decrementador:

public class Decrementador implements Runnable {
private int x;

public Decrementador(int x) {
this.x = x;
}

public void run() {
x=x-1;
}
}



Muchas gracias de antemano!

ivancea96

La razón de que actualmente no te funcione es que los tipos primitivos como int se copian cuando los pasas como parámetro. Al copiarse, es otro objeto diferente, y aunque lo modifiques, el original se queda igual. Para pasar objetos, tendrías que utilizar clases en vez de tipos primitivos.

Tendrías que meter la variable dentro de una clase y compartir un objeto de esa clase con las 2 que lo van a modificar. Otra opción es tenerlo como variable static y que accedan directamente a ella.

Dado que el acceso concurrente te acabaría dando problemas, puedes usar la clase AtomicInteger que sirve precisamente para eso: Modificar un int desde varios hilos. Podrías crearla en el main, y pasárselo al constructor de los otros 2 como haces ahora.

fileteruso


rub'n

#3
fileteruso que tal usa geshi, para que en un entorno de concurrencia un recurso compartido se actualize de manera correcta necesitas un lock con synchronized usandolo tanto en el método(no recomendado) y/O forma de bloque.

@ivancea96 con clases Atómicas no basta para que un recurso compartido sea actualizado correctamente por múltiples hilos a la misma ves, tampoco basta con volatile se necesita un lock, para asegurar la correcta sincronización.

Lo podemos ver con un ejemplo simple con 20 Hilos, linea 18, y usando un ExecutorService que es mejor que usar directamente la clase Thread o Runnable.



Código (java) [Seleccionar]

package foro

import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import java.util.stream.IntStream;

public class UsoSynchronized {

   private static final Logger LOGGER = Logger.getLogger(Principal.class.getSimpleName());
   private ExecutorService executors = null;
   private volatile int count = 0;
   private AtomicInteger atomicInteger = new AtomicInteger(0);

   public UsoSynchronized() {
       try {
           executors = Executors.newFixedThreadPool(20);
           IntStream.rangeClosed(1,10)
                   .forEach(e -> {
                       executors.submit(()-> withAtomicInteger());
                   });
       }finally {
           if(Objects.nonNull(executors)) {
               executors.shutdown(); // ayuda a evitar memory leaks, fugas de memoria
           }
       }
   }

   public void hola() {
       synchronized (this) {
          LOGGER.info("Hola " + (++count));
       }
   }

   public void withAtomicInteger() {
       //synchronized (this) {
           LOGGER.info(""+ atomicInteger.incrementAndGet());
       //}
   }

   public static void main(String... haga) {
       new UsoSynchronized();
   }
}

}



Código (bash) [Seleccionar]

Feb 06, 2019 10:48:29 PM foro withAtomicInteger
INFORMACIÓN: 5
Feb 06, 2019 10:48:29 PM foro withAtomicInteger
INFORMACIÓN: 10
Feb 06, 2019 10:48:29 PM foro withAtomicInteger
INFORMACIÓN: 9
Feb 06, 2019 10:48:29 PM foro withAtomicInteger
INFORMACIÓN: 8
Feb 06, 2019 10:48:29 PM foro withAtomicInteger
INFORMACIÓN: 7
Feb 06, 2019 10:48:29 PM foro withAtomicInteger
INFORMACIÓN: 6
Feb 06, 2019 10:48:29 PM foro withAtomicInteger
INFORMACIÓN: 2
Feb 06, 2019 10:48:29 PM foro withAtomicInteger
INFORMACIÓN: 3
Feb 06, 2019 10:48:29 PM foro withAtomicInteger
INFORMACIÓN: 4
Feb 06, 2019 10:48:29 PM foro withAtomicInteger
INFORMACIÓN: 1


Ese resultado de arriba es única y llanamente debido por que no se tiene un lock lo comente apropósito al invocar al método withAtomicInteger() en la linea 21, y cada Hilo entra a modificar el recurso count, e incluso lo ideal siempre es un bloque lock, es decir no usar synchronized en el método debido a que hay mas costo en el performance de ejecución, es mejor localizar justo el recurso que se modificara, tarea no tan fácil de hacer la mayoría de las veces



Otro ejemplo con 3 hilos, lo hize por la brevedad, nasty, usando atomic Integer y con lock comentado

Código (java) [Seleccionar]
public class UsoSynchronized2 {

   private int count= 0;
   private AtomicInteger atomicInteger = new AtomicInteger(0);

   public UsoSynchronized2() {

   }

   private void init(final String msg) {
      // synchronized (this) {
           System.out.println(msg + atomicInteger.incrementAndGet());
      // }
   }



   public static void main(String ...blabl) {
       UsoSynchronized2 usoSynchronized2 = new UsoSynchronized2();

       IntStream.rangeClosed(1,100)
               .forEach( e -> {
                   new Thread(() -> usoSynchronized2.init("Hilo 1 ")).start();
                   new Thread(() -> usoSynchronized2.init("Hilo 2 ")).start();
                   new Thread(() -> usoSynchronized2.init("Hilo 3 ")).start();
               });


   }


Código (bash) [Seleccionar]
Hilo 1 1
Hilo 2 2
Hilo 3 3
Hilo 3 4
Hilo 1 5
Hilo 2 6
Hilo 1 7
Hilo 2 8
Hilo 3 9
Hilo 3 10
Hilo 2 11
Hilo 3 12
Hilo 2 13
Hilo 1 14
Hilo 2 15
Hilo 1 16
Hilo 1 17
Hilo 2 18
Hilo 2 19
Hilo 1 20
Hilo 1 21
Hilo 2 22
Hilo 1 23
Hilo 2 24
Hilo 3 25
Hilo 1 26
Hilo 1 27
Hilo 3 28
Hilo 1 29
Hilo 1 30
Hilo 3 31
Hilo 1 32
Hilo 2 33
Hilo 2 34
Hilo 1 35
Hilo 3 36
Hilo 2 43
Hilo 3 42
Hilo 3 41
Hilo 2 40
Hilo 2 39
Hilo 1 38
Hilo 1 37
Hilo 2 44
Hilo 3 45
Hilo 1 46
Hilo 1 47
Hilo 1 48
Hilo 2 49
Hilo 3 50
Hilo 2 51
Hilo 2 52
Hilo 3 53
Hilo 1 54
Hilo 2 55
Hilo 2 56
Hilo 1 57
Hilo 1 60
Hilo 3 61
Hilo 2 59
Hilo 2 62
Hilo 3 58
Hilo 1 65
Hilo 2 66
Hilo 3 64
Hilo 1 63
Hilo 3 67
Hilo 1 68
Hilo 2 69
Hilo 3 70
Hilo 1 71
Hilo 1 72
Hilo 3 73
Hilo 3 75
Hilo 1 76
Hilo 3 77
Hilo 2 78
Hilo 2 74
Hilo 1 80
Hilo 1 81
Hilo 2 82
Hilo 3 79
Hilo 2 83
Hilo 1 84
Hilo 3 85
Hilo 1 88
Hilo 3 87
Hilo 2 86
Hilo 3 98
Hilo 1 104
Hilo 3 97
Hilo 2 96
Hilo 1 95
Hilo 2 109
Hilo 2 94
Hilo 3 93
Hilo 3 92
Hilo 1 91
Hilo 1 113
Hilo 1 114
Hilo 1 90
Hilo 1 89
Hilo 1 123
Hilo 1 132
Hilo 2 133
Hilo 3 121
Hilo 2 120
Hilo 1 119
Hilo 2 118
Hilo 2 117
Hilo 3 115
Hilo 2 116
Hilo 3 112
Hilo 2 111
Hilo 3 110
Hilo 3 108
Hilo 1 107
Hilo 3 106
Hilo 3 105
Hilo 1 163
Hilo 3 103
Hilo 1 101
Hilo 1 102
Hilo 3 100
Hilo 2 99
Hilo 2 164
Hilo 3 162
Hilo 3 165
Hilo 2 161
Hilo 2 167
Hilo 3 160
Hilo 1 159
Hilo 3 158
Hilo 3 169
Hilo 2 157
Hilo 3 156
Hilo 2 155
Hilo 3 173
Hilo 3 154
Hilo 1 153
Hilo 1 152
Hilo 2 151
Hilo 3 150
Hilo 3 149
Hilo 1 148
Hilo 2 147
Hilo 1 146
Hilo 3 145
Hilo 2 144
Hilo 3 177
Hilo 2 179
Hilo 3 143
Hilo 3 142
Hilo 2 141
Hilo 2 140
Hilo 3 139
Hilo 1 138
Hilo 2 137
Hilo 1 182
Hilo 3 136
Hilo 3 183
Hilo 2 135
Hilo 2 134
Hilo 2 131
Hilo 1 130
Hilo 3 129
Hilo 3 128
Hilo 3 127
Hilo 1 188
Hilo 2 126
Hilo 2 125
Hilo 1 124
Hilo 3 122
Hilo 3 190
Hilo 3 189
Hilo 2 187
Hilo 2 186
Hilo 3 185
Hilo 1 184
Hilo 2 181
Hilo 3 180
Hilo 2 178
Hilo 1 176
Hilo 1 175
Hilo 2 174
Hilo 2 172
Hilo 1 171
Hilo 1 170
Hilo 1 168
Hilo 3 166
Hilo 2 191
Hilo 1 192
Hilo 2 193
Hilo 3 194
Hilo 1 195
Hilo 3 196
Hilo 1 197
Hilo 2 198
Hilo 1 199
Hilo 2 200
Hilo 1 201
Hilo 2 202
Hilo 2 203
Hilo 2 204
Hilo 1 205
Hilo 3 206
Hilo 2 207
Hilo 2 208
Hilo 3 209
Hilo 3 210
Hilo 1 211
Hilo 1 212
Hilo 1 213
Hilo 2 214
Hilo 1 215
Hilo 3 216
Hilo 2 217
Hilo 1 218
Hilo 3 219
Hilo 2 220
Hilo 2 221
Hilo 3 222
Hilo 3 223
Hilo 2 224
Hilo 1 225
Hilo 3 226
Hilo 1 227
Hilo 3 228
Hilo 2 229
Hilo 2 230
Hilo 1 231
Hilo 2 232
Hilo 3 233
Hilo 1 234
Hilo 3 235
Hilo 1 236
Hilo 3 237
Hilo 1 238
Hilo 2 239
Hilo 3 240
Hilo 1 241
Hilo 3 242
Hilo 1 243
Hilo 1 244
Hilo 3 245
Hilo 2 246
Hilo 1 247
Hilo 2 248
Hilo 3 249
Hilo 1 250
Hilo 2 251
Hilo 3 252
Hilo 1 253
Hilo 2 254
Hilo 3 255
Hilo 1 256
Hilo 2 257
Hilo 3 258
Hilo 2 259
Hilo 3 260
Hilo 1 261
Hilo 1 262
Hilo 2 263
Hilo 3 264
Hilo 2 265
Hilo 3 266
Hilo 1 267
Hilo 2 268
Hilo 3 269
Hilo 1 270
Hilo 3 271
Hilo 1 272
Hilo 2 273
Hilo 3 274
Hilo 2 275
Hilo 3 276
Hilo 1 277
Hilo 3 278
Hilo 1 279
Hilo 2 280
Hilo 1 281
Hilo 3 282
Hilo 2 283
Hilo 3 284
Hilo 1 285
Hilo 2 286
Hilo 3 287
Hilo 2 288
Hilo 2 289
Hilo 1 290
Hilo 1 291
Hilo 1 292
Hilo 3 294
Hilo 3 293
Hilo 3 296
Hilo 2 295
Hilo 2 297
Hilo 3 298
Hilo 2 299
Hilo 1 300


caso distinto tendriamos si usamos el lock

Código (java) [Seleccionar]
private void init(final String msg) {
       synchronized (this) {
           System.out.println(msg + atomicInteger.incrementAndGet());
       }
   }

Código (bash) [Seleccionar]
Hilo 1 1
Hilo 3 2
Hilo 2 3
Hilo 2 4
Hilo 1 5
Hilo 1 6
Hilo 2 7
Hilo 1 8
Hilo 3 9
Hilo 2 10
Hilo 1 11
Hilo 2 12
Hilo 1 13
Hilo 3 14
Hilo 2 15
Hilo 3 16
Hilo 2 17
Hilo 1 18
Hilo 2 19
Hilo 3 20
Hilo 1 21
Hilo 3 22
Hilo 2 23
Hilo 3 24
Hilo 1 25
Hilo 3 26
Hilo 2 27
Hilo 1 28
Hilo 3 29
Hilo 3 30
Hilo 2 31
Hilo 1 32
Hilo 3 33
Hilo 1 34
Hilo 2 35
Hilo 3 36
Hilo 2 37
Hilo 1 38
Hilo 3 39
Hilo 2 40
Hilo 2 41
Hilo 1 42
Hilo 1 43
Hilo 2 44
Hilo 3 45
Hilo 3 46
Hilo 2 47
Hilo 1 48
Hilo 2 49
Hilo 1 50
Hilo 2 51
Hilo 3 52
Hilo 1 53
Hilo 3 54
Hilo 1 55
Hilo 3 56
Hilo 2 57
Hilo 1 58
Hilo 2 59
Hilo 3 60
Hilo 3 61
Hilo 2 62
Hilo 1 63
Hilo 2 64
Hilo 1 65
Hilo 3 66
Hilo 3 67
Hilo 1 68
Hilo 2 69
Hilo 3 70
Hilo 1 71
Hilo 1 72
Hilo 2 73
Hilo 3 74
Hilo 3 75
Hilo 2 76
Hilo 1 77
Hilo 3 78
Hilo 2 79
Hilo 1 80
Hilo 3 81
Hilo 2 82
Hilo 1 83
Hilo 3 84
Hilo 2 85
Hilo 1 86
Hilo 3 87
Hilo 2 88
Hilo 1 89
Hilo 3 90
Hilo 3 91
Hilo 2 92
Hilo 1 93
Hilo 1 94
Hilo 3 95
Hilo 2 96
Hilo 3 97
Hilo 1 98
Hilo 2 99
Hilo 3 100
Hilo 1 101
Hilo 2 102
Hilo 2 103
Hilo 3 104
Hilo 1 105
Hilo 3 106
Hilo 1 107
Hilo 2 108
Hilo 1 109
Hilo 1 110
Hilo 3 111
Hilo 1 112
Hilo 1 113
Hilo 2 114
Hilo 3 115
Hilo 3 116
Hilo 3 117
Hilo 1 118
Hilo 1 119
Hilo 2 120
Hilo 3 121
Hilo 1 122
Hilo 2 123
Hilo 3 124
Hilo 1 125
Hilo 2 126
Hilo 3 127
Hilo 2 128
Hilo 2 129
Hilo 2 130
Hilo 3 131
Hilo 1 132
Hilo 2 133
Hilo 3 134
Hilo 1 135
Hilo 2 136
Hilo 3 137
Hilo 1 138
Hilo 2 139
Hilo 3 140
Hilo 1 141
Hilo 2 142
Hilo 1 143
Hilo 2 144
Hilo 3 145
Hilo 2 146
Hilo 3 147
Hilo 2 148
Hilo 1 149
Hilo 2 150
Hilo 3 151
Hilo 1 152
Hilo 3 153
Hilo 3 154
Hilo 1 155
Hilo 2 156
Hilo 1 157
Hilo 2 158
Hilo 3 159
Hilo 2 160
Hilo 1 161
Hilo 3 162
Hilo 2 163
Hilo 3 164
Hilo 2 165
Hilo 3 166
Hilo 1 167
Hilo 1 168
Hilo 1 169
Hilo 3 170
Hilo 2 171
Hilo 3 172
Hilo 3 173
Hilo 1 174
Hilo 1 175
Hilo 3 176
Hilo 1 177
Hilo 2 178
Hilo 1 179
Hilo 3 180
Hilo 1 181
Hilo 3 182
Hilo 1 183
Hilo 2 184
Hilo 3 185
Hilo 3 186
Hilo 1 187
Hilo 2 188
Hilo 3 189
Hilo 1 190
Hilo 2 191
Hilo 2 192
Hilo 1 193
Hilo 2 194
Hilo 2 195
Hilo 3 196
Hilo 1 197
Hilo 3 198
Hilo 2 199
Hilo 2 200
Hilo 2 201
Hilo 2 202
Hilo 1 203
Hilo 2 204
Hilo 3 205
Hilo 1 206
Hilo 2 207
Hilo 2 208
Hilo 1 209
Hilo 3 210
Hilo 3 211
Hilo 3 212
Hilo 1 213
Hilo 2 214
Hilo 2 215
Hilo 2 216
Hilo 1 217
Hilo 3 218
Hilo 1 219
Hilo 3 220
Hilo 3 221
Hilo 1 222
Hilo 1 223
Hilo 1 224
Hilo 1 225
Hilo 1 226
Hilo 2 227
Hilo 2 228
Hilo 3 229
Hilo 2 230
Hilo 3 231
Hilo 2 232
Hilo 2 233
Hilo 3 234
Hilo 1 235
Hilo 2 236
Hilo 1 237
Hilo 2 238
Hilo 2 239
Hilo 3 240
Hilo 3 241
Hilo 3 242
Hilo 3 243
Hilo 3 244
Hilo 1 245
Hilo 3 246
Hilo 2 247
Hilo 2 248
Hilo 2 249
Hilo 3 250
Hilo 1 251
Hilo 2 252
Hilo 3 253
Hilo 3 254
Hilo 1 255
Hilo 3 256
Hilo 2 257
Hilo 1 258
Hilo 1 259
Hilo 1 260
Hilo 2 261
Hilo 2 262
Hilo 2 263
Hilo 3 264
Hilo 1 265
Hilo 1 266
Hilo 2 267
Hilo 1 268
Hilo 3 269
Hilo 2 270
Hilo 1 271
Hilo 1 272
Hilo 2 273
Hilo 3 274
Hilo 3 275
Hilo 1 276
Hilo 2 277
Hilo 3 278
Hilo 1 279
Hilo 2 280
Hilo 3 281
Hilo 1 282
Hilo 2 283
Hilo 1 284
Hilo 3 285
Hilo 1 286
Hilo 3 287
Hilo 1 288
Hilo 3 289
Hilo 2 290
Hilo 3 291
Hilo 1 292
Hilo 3 293
Hilo 1 294
Hilo 2 295
Hilo 2 296
Hilo 1 297
Hilo 3 298
Hilo 3 299
Hilo 2 300


la sincronizacion tiene un efecto bastante duro por el abuso del mismo,DEADLOCK, cuando varios hilos necesitan acceso exclusivo al recurso bloqueandolo/manteniendo el bloqueo, siendo también un bug difícil de detectar  >:D


rubn0x52.com KNOWLEDGE  SHOULD BE FREE!!!
If you don't have time to read, you don't have the time (or the tools) to write, Simple as that. Stephen

fileteruso

Gracias por una respuesta tan elaborada, pero justamente lo que pretendo no es que funcione de manera sincronizada, sino que falle y provocar una condición de carrera. La finalidad es meramente académica.

Muchísimas gracias otra vez.

fileteruso

#5
Cita de: ivancea96 en  6 Febrero 2019, 20:54 PM
La razón de que actualmente no te funcione es que los tipos primitivos como int se copian cuando los pasas como parámetro. Al copiarse, es otro objeto diferente, y aunque lo modifiques, el original se queda igual. Para pasar objetos, tendrías que utilizar clases en vez de tipos primitivos.

Tendrías que meter la variable dentro de una clase y compartir un objeto de esa clase con las 2 que lo van a modificar. Otra opción es tenerlo como variable static y que accedan directamente a ella.

Dado que el acceso concurrente te acabaría dando problemas, puedes usar la clase AtomicInteger que sirve precisamente para eso: Modificar un int desde varios hilos. Podrías crearla en el main, y pasárselo al constructor de los otros 2 como haces ahora.

Al final lo he resuelto pasando la propia clase como has dicho. No me ha hecho falta usar AtomicInteger.

Dejo el código para quien pudiera estar interesado:

Clase principal:

public class Principal {
public volatile static int x;

public Principal() {
x = 0;
}

public static void main(String[] args) throws InterruptedException {
Incrementador[] inc = new Incrementador[1000];
Decrementador[] dec = new Decrementador[1000];

Thread hiloinc;
Thread hilodec;

for(int i=0; i<1000; i++) {
inc[i] = new Incrementador();
dec[i] = new Decrementador();

hiloinc = new Thread(inc[i]);
hilodec = new Thread(dec[i]);

hiloinc.start();
hilodec.start();

hiloinc.join();
hilodec.join();
}

System.out.println(x);
}
}


Clase Incrementador:

public class Incrementador implements Runnable {
public void run() {
Principal.x = Principal.x+1;
}
}


Clase Decrementador:


public class Decrementador implements Runnable {
public void run() {
Principal.x = Principal.x-1;
}
}



Gracias a todos!

ivancea96

Cita de: rub'n en  6 Febrero 2019, 22:57 PM
@ivancea96 con clases Atómicas no basta para que un recurso compartido sea actualizado correctamente por múltiples hilos a la misma ves, tampoco basta con volatile se necesita un lock, para asegurar la correcta sincronización.

Las clases del paquete atomic son precisamente thread-safe y lock-free. Son una optimización al uso de locks, y su uso es precisamente este.

No sé que pretendías ver en tu ejemplo, pero el resultado es el esperado: La variable ha sido incrementada 10 veces, por tanto, es 10, punto.

Puedes verificarlo si quieres:

Código (cpp) [Seleccionar]
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import java.util.stream.IntStream;

public class UsoSynchronized {

    private static final Logger LOGGER = Logger.getLogger(UsoSynchronized.class.getSimpleName());
    private ExecutorService executors = null;
    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public UsoSynchronized() {
        try {
            executors = Executors.newFixedThreadPool(20);
            IntStream.rangeClosed(1,10)
                    .forEach(e -> {
                        executors.submit(()-> withAtomicInteger());
                    });

        } finally {
            if(Objects.nonNull(executors)) {
                executors.shutdown();
               
                try {
                    executors.awaitTermination(1, TimeUnit.DAYS);
                } catch (InterruptedException exc) {
                }
            }
        }
       
        LOGGER.info("Final: "+ atomicInteger.get());
    }

    public void withAtomicInteger() {
        LOGGER.info(""+ atomicInteger.incrementAndGet());
    }

    public static void main(String... haga) {
        new UsoSynchronized();
    }
}


Verás un 10 en Final, es lo esperado, y verás los números del 1 al 10 antes que él.

Si tú te refieres al orden, evidentemente el orden no es una necesidad en este caso, y como tal, no se mantiene.

rub'n

Claro, no me niego tampoco a las clases Atómicas, seria ilógico que no me diera cuenta del resultado si en el mensaje puse el output donde se ve clarisimamente el 10

Ese ejemplo lo puse mas bien pensando a futuro en fileteruso, porque estoy 100% que le tocara aplicar un lock  ::)

dog.




rubn0x52.com KNOWLEDGE  SHOULD BE FREE!!!
If you don't have time to read, you don't have the time (or the tools) to write, Simple as that. Stephen