Synchronized(Object)... Come si usa???

di il
10 risposte

Synchronized(Object)... Come si usa???

Salve ragazzi, vi scrivo perchè, nel capire il multithreading in java, mi sono imbattuto nella clausola synchronized...

credo di aver ben capito quando un methodo sia synchronized come funzioni...

Ciò che non capisco è quando lo si usa su altri oggetti:

ad esempio ho un metodo add al cui interno c'è un blocco synchronized del tipo:
public void add(){


	synchronized(this){
	
		c++;
	}
}
quel synchronized sta a dire che io, thread, che sono in questo blocco, ottengo il lock su questo monitor, dove this è il monitor, giusto?


ma cosa significa invece scrivere synchronized(object) dove object sia diverso da this?

vi posto questo codice:
public class MyRunnable implements Runnable {

	private static boolean activated=true;
	private Thread MyThread;
	private int MyMode;
	private static int RunCounts=0;
	static final private Counter MyCounter= new MyRunnable.Counter();
	
	static class Counter{
		
		int Counter=0;
		
		public void increment(){
			
			Counter++;
			System.out.println(" Incremento a: "+ Counter);
			try {
				
				Thread.sleep(1000);
				//notify();
			}
			catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		public void decrement(){			
			
			Counter--;
			System.out.println(Thread.currentThread().getName() + " has decreased Counter value to: " + Counter);
		}
		
		public int getCounter(){			
				
			return Counter;
		}
	}
	
	public MyRunnable(int Mode){
		
		MyMode=Mode;
		RunCounts++;
		MyThread= new Thread(this,"RunCounts" + RunCounts);
		MyThread.start();
	}
	
	@Override
	public void run() {

		while (activated){
			
			System.out.print("\n"+Thread.currentThread().getName() + " attempting to get into SYNCHRO ZONE...\n");
			
			synchronized(MyCounter){
				
				System.out.print(Thread.currentThread().getName() + " INSIDE SYNCHRO ZONE...\n");
				if (MyMode==1){
					
					System.out.print("[Increment] - aumento...\n");
					try {
						Thread.sleep(2000);
						MyCounter.increment();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				else if (MyMode==2){

					System.out.print("[Decrement] - decremento...\n");
                                        MyCounter.decrement();
					try {
						
						Thread.sleep(5000);
					}
					catch (InterruptedException e) {
					
						e.printStackTrace();
					}					
				}

			}
			System.out.print(Thread.currentThread().getName() + " OUTSIDE SYNCHRO ZONE...\n");
		}
	}

	public void activate(boolean b) {
		
		activated=b;
	}
}
tutto funziona bene usando MyCounter in tale maniera, ma non ho ben capito come... infatti se provo ad usare un int normale ad esempio e scrivere synchronized((Integer)C) non funziona...

idem per la classe AtomicInteger, usata giusto per provarla... Potete darmi una mano? Ho l'esame lunedì

Vi ringrazio in anticipo!!!

10 Risposte

  • Re: Synchronized(Object)... Come si usa???

    La stessa identica cosa.

    In Java qualunque oggetto puo' essere usato come oggetto di sincronizzazione.

    In un classe synchronized, automaticamente tutti i metodi sono synchronized

    Un metodo synchronized acquisisce un lock su this automaticamente: invece di farlo tu a mano, lo inseriche il ocmpilator.

    Per il resto, devi studiare che cosa e' la programmazione concorrente e quali sono gli oggetti di sincronizzazione. Ne esitono diversi con diverse proprieta'.

    Il lock su un oggetto e' solo il mattone fondamentale per implementare tutti gli altri.
  • Re: Synchronized(Object)... Come si usa???

    migliorabile ha scritto:


    In un classe synchronized, automaticamente tutti i metodi sono synchronized.

    quindi potrei tranquillamente scrivere questo?:
    public void run() {
    
    		while (activated){
    			
    			System.out.print("\n"+Thread.currentThread().getName() + " attempting to get into SYNCHRO ZONE...\n");
    			
    			synchronized([b]MyMode[/b]){
    				
    				System.out.print(Thread.currentThread().getName() + " INSIDE SYNCHRO ZONE...\n");
    				if ([b]MyMode.intValue()==1[/b]){
    					
    					System.out.print("[Increment] - aumento...\n");
    					try {
    						Thread.sleep(2000);
    						MyCounter.increment();
    						
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    				}
    				else if ([b]MyMode.intValue()==2[/b]){
    					
    					System.out.print("[Decrement] - decremento...\n");
    					
    					try {
    						
    						Thread.sleep(5000);
                                                    MyCounter.decrement();
    					}
    					catch (InterruptedException e) {
    					
    						e.printStackTrace();
    					}
    					
    					
    				}
    
    			}
    			System.out.print(Thread.currentThread().getName() + " OUTSIDE SYNCHRO ZONE...\n");
    		}
    	}
    
    P.S= ho usato un oggetto nel synchronized che non c'entra nulla, però dato che ci accedo tramite i suoi metodi, che a questo punto dovrebbero essere impostati a synchronized, perchè i flussi si accavallano?


    forse perchè la classe Integer è particolare e quello che ho fatto andrebbe bene per qualunque altro tipo di oggetto?
  • Re: Synchronized(Object)... Come si usa???

    Non inventarti spiegazioni arzigogolate.

    Il fatto di usare un oggetto con la keyword synchronized non centra nulla con i suoi metodi!

    Quello che e' sincronizzato, e' l'acquisizione del lock su quell'oggetto! STOP!

    Se un thread ha acquisito un lock su un oggetto, nessun altro thread puo' acquisire un lock sullo stesso oggetto!

    Tutto il ragionamento si ferma qui!

    ATTENZIONE: DEVE ESSERE LO STESSO OGGETTO, se vuoi, piu' precisamente, la stessa istanza!

    L'altra questione e' che la classe deve essere dichiarata syncronized, e non ha nessuna relazione con quello che hai scritto, cioe' aver acquisito un lock so un'istanza di un oggetto!

    La classe Integer non e' dichiarata syncronized!
  • Re: Synchronized(Object)... Come si usa???

    Francesco93 ha scritto:


    ad esempio ho un metodo add al cui interno c'è un blocco synchronized del tipo:
    public void add(){
    
    
    	synchronized(this){
    	
    		c++;
    	}
    }
    quel synchronized sta a dire che io, thread, che sono in questo blocco, ottengo il lock su questo monitor, dove this è il monitor, giusto?
    Sì, giusto. Ogni oggetto, di qualunque classe (anche di array) possiede quello che la documentazione tecnica ufficiale chiama "monitor" (ci sono ragioni storiche, se non sbaglio, riguardo questo nome ma dovrei andare a rileggere).

    Solamente un thread per volta può acquisire il monitor di un oggetto. Questo permette di rendere "atomiche" una serie di istruzioni rispetto alla concorrenza tra i thread. In sostanza impone una "serializzazione" tra i thread per la esecuzione di quel blocco di codice sincronizzato.

    Francesco93 ha scritto:


    ma cosa significa invece scrivere synchronized(object) dove object sia diverso da this?
    Cambia solo rispetto a cosa è "sincronizzato" un blocco di codice. Generalmente si mette synchronized sui metodi di istanza, quindi il monitor è sostanzialmente il this. Questo vuol anche dire che l'oggetto di lock è "pubblico", chiunque ne ha il reference può acquisire quel monitor.

    Ci sono casi/scenari in cui è necessario che il monitor sia nascosto, per varie ragioni. E allora si fa in genere una cosa tipo:
    public class UnaClasse {
        private final Object lockObj = new Object();
    
        public void metodoA() {
            synchronized (lockObj) {
                // .......
            }
        }
    
        public void metodoB() {
            synchronized (lockObj) {
                // .......
            }
        }
    }
    In questo modo l'oggetto di lock non è più il this ma quel lockObj. Che essendo private è quindi nascosto. Chi ha il riferimento ad un oggetto UnaClasse non può più pensare di fare
    synchronized (oggUnaClasse) {
        oggUnaClasse.metodoA();
    }
    perché non impedirebbe ad un altro thread di eseguire nel frattempo direttamente metodoB() su quello stesso oggetto.
    Non sto a dilungarmi, ripeto che ci possono essere ragioni davvero valide per nascondere il monitor.

    Quando vedi synchronized in un codice innanzitutto devi determinare quale è l'oggetto di lock, comprendere il suo scope (chi/dove lo può usare) e a quel punto pensare semplicemente che quello farà da "serializzatore" degli accessi.

    Stesso oggetto di lock = mutua esclusione. Tutto qui.

    Francesco93 ha scritto:


    vi posto questo codice:

    tutto funziona bene usando MyCounter in tale maniera, ma non ho ben capito come...
    Il codice che hai postato è un po' "contorto", per la struttura ma anche per le denominazioni (una classe Counter e una variabile ... Counter).

    Comunque tieni presente che ++ e -- NON sono "atomici". Sono 3 operazioni distinte fatte dalla JVM: leggi, incrementa, scrivi. Quindi in un'ottica di concorrenza tra thread, ++/-- vanno sempre fatti in modo sincronizzato, altrimenti potresti avere una race-condition (due thread leggono e incrementano lo stesso valore).

    Nel codice comunque una cosa ti deve risultare chiara. L'oggetto di lock è una istanza di MyRunnable.Counter. Ce n'è uno solo, siccome è tenuto in un campo static, quindi anche avendo N istanze di MyRunnable il lock è sempre lo stesso. Pertanto tutto il codice in quel blocco synchronized è assolutamente in mutua esclusione rispetto a tutte le istanze di MyRunnable.

    Francesco93 ha scritto:


    infatti se provo ad usare un int normale ad esempio e scrivere synchronized((Integer)C) non funziona...
    Se C è un int, allora quello è un auto-boxing (Java 5 in poi). La implementazione dell'auto-boxing mette in atto una forma di caching degli oggetti ma solo per un certo range molto ristretto di valori.
    Se C vale 10, ad ogni passaggio in quel codice, l'auto-boxing ti fornisce lo stesso medesimo oggetto Integer. Se C valesse 1000, allora no, crea sempre nuovi oggetti Integer. Oggetti di lock diversi ... niente mutua esclusione. Stop.

    Quindi no, non ha senso fare una cosa del genere, non è proprio da fare.
  • Re: Synchronized(Object)... Come si usa???

    migliorabile ha scritto:


    In un classe synchronized, automaticamente tutti i metodi sono synchronized
    Piccola correzione ma doverosa: synchronized non può essere applicato ai tipi (classi, interfacce ...).
  • Re: Synchronized(Object)... Come si usa???

    Grazie mille andbin, e grazie mille a tutti gli altri

    Ho finalmente capito come usare il synchronized... la scelta di crearmi un lockObj, ad esempio, mi è piaciuta e l'ho capita facilmente .

    Perdonatemi per il codice un po' contorto, ma sono un amatore del C e C++ e ho appena iniziato col java, scrivere le variabili in minuscolo mi da un po' fastidio eheh...

    ora però c'è solo un problema.. ho provato a fare questo semplice codice:

    CLASSE THREAD:
    package Threads.Thread_1;
    
    public class Thread_1 extends Thread {
    
        private MyInt value=new MyInt();
    
        public Thread_1(String string) {
    
        	super(string);
    	}
    
    	public void run() {
            
        	while (true) {
                
        		synchronized(this){
        			
        			System.out.println("La mutua esclusione funziona perchè nel main ho creato un lock riferito esattamente a this.\n");
        			value.setValue(234);
        			notify();
        		}
        		
            }
        }
    
        public int getValue() {
            
        	return value.getValue();
        }
    }
    
    class MyInt{
    	
    	int value;
    	
    	public void setValue(int value){
    			
    		this.value=value;
    	}
    	
    	public int getValue(){
    
    		return value;
    	}
    }
    CLASSE MAIN:
    package Threads.Thread_1;
    
    public class Main_1 {
    
    	public static void main(String[] args){
    	    
    		Thread_1 runner = new Thread_1("MyThread");
    		int runnerValue=0;
    	    
    		synchronized (runner){
    			
    	    	try {
    
    	    		runner.start();
    	    		runner.wait();
    	    		runnerValue=runner.getValue(); // Will I ever get the value?
    			}
    		    catch (InterruptedException e) {
    			
    				e.printStackTrace();
    			}
    	    }
    		    
    	    
    	    System.out.println(runnerValue);
    	}
    }
    
    Ho voluto usare il synchronized con wait e notify e tutto fila liscio come l'olio... tranne quando nella parte relativa alla notify inserisco delle println o qualunque altra cosa... come mai secondo voi?? :/
  • Re: Synchronized(Object)... Come si usa???

    Francesco93 ha scritto:


    Grazie mille andbin, e grazie mille a tutti gli altri
    Prego, rispondo anche qui .. mi stavo quasi dimenticando.

    Però non mi è chiaro cosa intendi quando dici:

    Francesco93 ha scritto:


    tranne quando nella parte relativa alla notify inserisco delle println o qualunque altra cosa... come mai secondo voi?? :/
    Il codice che hai postato comunque è sensato. Io ti spiego cosa succede.

    Il main crea un Thread_1 e prima di farlo partire acquisire il lock su di esso. Quell'oggetto Thread_1, pur essendo qualcosa di più particolare (un java.lang.Thread) è comunque un oggetto come un altro, quindi il lock è ovviamente acquisibile.

    Poi invoca start() e wait(). Lo start mette il thread solo in stato "runnable", non fa eseguire di per sé il run(). Il wait invece deve essere invocato su un oggetto di cui si possiede il monitor. E infatti è così.

    Quel wait mette il "main" thread in attesa di essere risvegliato (da un notify/notifyAll). Ma attenzione: wait rilascia il lock! (altrimenti un altro thread non potrebbe acquisire quel lock e invocare notify).

    Il nuovo thread riesce quindi sicuramente ad entrare nel synchronized(this) del run. Quindi setta il valore e notifica (risveglia) un thread. Visto che l'unico thread in attesa su quella condition queue è il main thread, esso quindi può certamente risvegliarsi.

    Quando si esce dal wait(), il thread deve competere per riacquisire il lock. Questo è possibile solo quando il blocco synchronized(this) del run rilascia il lock. E anche questo succede sicuramente.

    In sostanza, è sicuramente possibile che il main thread riesca ad arrivare a fare quel getValue(). Ma il "quando" (per meglio dire, dopo quanto tempo), dipende solo dal modo di schedulazione dei thread e soprattutto dalle tempistiche più o meno "fortunate" che capitano.


    P.S. tieni comunque presente che il wait non va generalmente usato così. Lo si dovrebbe usare in un ciclo che testa una certa condizione che è quella che deve "tenere" per far stare il thread in attesa.
    Pensa ad una coda "bloccante" con lunghezza limitata dove un put() si blocca se è piena:
    public synchronized void put(Tipo elemento) {
        ......
        while (condizione_coda_piena) {
            wait();
        }
        ......// aggiungi alla coda
    }
    Questo è più corretto.
  • Re: Synchronized(Object)... Come si usa???

    Ho compreso tutto ciò che hai detto, solo che eseguendo questo codice, senza la println si esegue senza problemi e posso stampare il valore di runnervalue, invece con la println questo codice mostra come la notify non sia affatto immediata...

    per spiegarti ti mostro quest'immagine:

    immagine.png
    immagine.png

    io pensavo che così facendo, avrei visto UNA riga stampare la println, e successivamente il numero 234.

    Invece ci mette molto tempo prima di poter vedere il 234... c'è un modo per rendere più immediata tale procedura? :/

    grazie per l'aiuto
  • Re: Synchronized(Object)... Come si usa???

    Ciao,
    questo un esempio su come si usa syncronized:
    http://www.iljavarolo.com/java-syncronized-come-si-usa-esempio/
  • Re: Synchronized(Object)... Come si usa???

    Credo non abbia molto senso andare a rispondere ad un thread morto e sepolto due anni fa....
Devi accedere o registrarti per scrivere nel forum
10 risposte