Dovresti sincronizzare il metodo di esecuzione? Perché o perché no?

Ho sempre pensato che la sincronizzazione del metodo run in una class java che implementa Runnable sia ridondante. Sto cercando di capire perché le persone fanno questo:

public class ThreadedClass implements Runnable{ //other stuff public synchronized void run(){ while(true) //do some stuff in a thread } } } 

Sembra ridondante e non necessario poiché stanno ottenendo il blocco dell’object per un altro thread. O piuttosto, stanno rendendo esplicito che solo un thread ha accesso al metodo run (). Ma poiché è il metodo run, non è esso stesso il suo thread? Pertanto, solo esso può accedere a se stesso e non ha bisogno di un meccanismo di blocco separato?

Ho trovato un suggerimento online che sincronizzando il metodo run potresti potenzialmente creare una coda thread di fatto, ad esempio, facendo ciò:

  public void createThreadQueue(){ ThreadedClass a = new ThreadedClass(); new Thread(a, "First one").start(); new Thread(a, "Second one, waiting on the first one").start(); new Thread(a, "Third one, waiting on the other two...").start(); } 

Non lo farei mai personalmente, ma si presta alla domanda sul perché qualcuno dovrebbe sincronizzare il metodo di esecuzione. Qualche idea perché o perché non si dovrebbe sincronizzare il metodo di esecuzione?

La sincronizzazione del metodo run() di Runnable è completamente inutile a meno che non si voglia condividere il Runnable tra più thread e si desideri sequenzialmente l’esecuzione di quei thread. Che è fondamentalmente una contraddizione in termini.

C’è in teoria un altro scenario molto più complicato in cui si potrebbe voler sincronizzare il metodo run() , che di nuovo comporta la condivisione del Runnable tra più thread, ma fa anche uso di wait() e notify() . Non l’ho mai incontrato in oltre 14 anni di Java.

C’è un vantaggio nell’usare synchronized void blah() su void blah() { synchronized(this) { e questo è il bytecode risultante sarà 1 byte più corto, poiché la sincronizzazione farà parte della firma del metodo invece di un’operazione da sola . Ciò potrebbe influenzare la possibilità di inline del metodo dal compilatore JIT. A parte questo non c’è differenza.

L’opzione migliore è utilizzare un private final Object lock = new Object() interno private final Object lock = new Object() per impedire a qualcuno di bloccare potenzialmente il monitor. Raggiunge lo stesso risultato senza il rovescio della malvagità al di fuori del blocco. Hai quel byte in più, ma raramente fa la differenza.

Quindi direi di no, non usare la parola chiave synchronized nella firma. Invece, usa qualcosa come

 public class ThreadedClass implements Runnable{ private final Object lock = new Object(); public void run(){ synchronized(lock) { while(true) //do some stuff in a thread } } } } 

Modifica in risposta al commento:

Considerare cosa fa la sincronizzazione: impedisce ad altri thread di entrare nello stesso blocco di codice. Quindi immagina di avere una class come quella qui sotto. Diciamo che la dimensione attuale è 10. Qualcuno prova ad eseguire un add e forza un ridimensionamento dell’array di supporto. Mentre sono nel mezzo del ridimensionamento dell’array, qualcuno chiama un makeExactSize(5) su un thread diverso. Ora all’improvviso stai provando ad accedere ai data[6] e ti bombardano. La sincronizzazione dovrebbe impedire che ciò accada. Nei programmi con multithreading è sufficiente la sincronizzazione NEED.

 class Stack { int[] data = new int[10]; int pos = 0; void add(int inc) { if(pos == data.length) { int[] tmp = new int[pos*2]; for(int i = 0; i < pos; i++) tmp[i] = data[i]; data = tmp; } data[pos++] = inc; } int remove() { return data[pos--]; } void makeExactSize(int size) { int[] tmp = new int[size]; for(int i = 0; i < size; i++) tmp[i] = data[i]; data = tmp; } } 

Perché? Minima sicurezza extra e non vedo alcun scenario plausibile in cui farebbe la differenza.

Perchè no? Non è standard. Se stai codificando come parte di un team, quando un altro utente vede la tua run sincronizzata probabilmente sprecherà 30 minuti cercando di capire cosa è così speciale sia con la tua run o con il framework che stai usando per eseguire i Runnable .

Dalla mia esperienza, non è utile aggiungere parole chiave “sincronizzate” al metodo run (). Se abbiamo bisogno di sincronizzare più thread, o abbiamo bisogno di una coda thread-safe, possiamo usare componenti più appropriati, come ConcurrentLinkedQueue.

Beh, potreste teoricamente chiamare il metodo di esecuzione stesso senza problemi (dopotutto è pubblico). Ma questo non significa che uno dovrebbe farlo. Quindi in pratica non c’è motivo di farlo, oltre ad aggiungere un overhead trascurabile al thread calling run (). Beh, eccetto se si usa l’istanza più volte per chiamare il new Thread – sebbene io sia a) non sono sicuro che sia legale con l’API di threading eb) sembra completamente inutile.

Anche il tuo createThreadQueue non funziona. synchronized su un metodo non statico si sincronizza sull’object istanza (cioè this ), quindi tutti e tre i thread verranno eseguiti in parallelo.

Passare attraverso i commenti e il commento del codice ed eseguire i diversi blocchi per vedere chiaramente la differenza, nota che la sincronizzazione avrà una differenza solo se viene utilizzata la stessa istanza eseguibile, se ogni thread avviato ottiene un nuovo eseguibile non farà alcuna differenza.

 class Kat{ public static void main(String... args){ Thread t1; // MyUsualRunnable is usual stuff, only this will allow concurrency MyUsualRunnable m0 = new MyUsualRunnable(); for(int i = 0; i < 5; i++){ t1 = new Thread(m0);//*imp* here all threads created are passed the same runnable instance t1.start(); } // run() method is synchronized , concurrency killed // uncomment below block and run to see the difference MySynchRunnable1 m1 = new MySynchRunnable1(); for(int i = 0; i < 5; i++){ t1 = new Thread(m1);//*imp* here all threads created are passed the same runnable instance, m1 // if new insances of runnable above were created for each loop then synchronizing will have no effect t1.start(); } // run() method has synchronized block which lock on runnable instance , concurrency killed // uncomment below block and run to see the difference /* MySynchRunnable2 m2 = new MySynchRunnable2(); for(int i = 0; i < 5; i++){ // if new insances of runnable above were created for each loop then synchronizing will have no effect t1 = new Thread(m2);//*imp* here all threads created are passed the same runnable instance, m2 t1.start(); }*/ } } class MyUsualRunnable implements Runnable{ @Override public void run(){ try {Thread.sleep(1000);} catch (InterruptedException e) {} } } class MySynchRunnable1 implements Runnable{ // this is implicit synchronization //on the runnable instance as the run() // method is synchronized @Override public synchronized void run(){ try {Thread.sleep(1000);} catch (InterruptedException e) {} } } class MySynchRunnable2 implements Runnable{ // this is explicit synchronization //on the runnable instance //inside the synchronized block // MySynchRunnable2 is totally equivalent to MySynchRunnable1 // usually we never synchronize on this or synchronize the run() method @Override public void run(){ synchronized(this){ try {Thread.sleep(1000);} catch (InterruptedException e) {} } } } 

in realtà è davvero facile giustificare “sincronizzare o non sincronizzare”

se l’invocazione del metodo può modificare lo stato interno dell’object, quindi “sincronizzare” altrimenti non è necessario

semplice esempio

 public class Counter { private int count = 0; public void incr() { count++; } public int getCount() { return count; } } 

nell’esempio sopra, incr () deve essere sincronizzato, poiché cambierà la val di conteggio, mentre la sincronizzazione getCount () non è necessaria

tuttavia c’è un altro caso d’angolo, se il conteggio è java.lang.Long, Double, Object allora devi dichiararlo come

 private volatile long count = 0; 

per assicurarsi che l’aggiornamento di ref sia atomico

fondamentalmente è quello che devi pensare al 99% del tempo quando si ha a che fare con il multithreading