- È necessario comprendere bene in che maniera i metodi synchronized sono eseguiti in mutua esclusione
- Per questo motivo presentiamo un esempio, semplice, che al variare di alcuni semplici keyword si comporta in maniera diversa
- Uno strumento di “lavoro” per fare pratica.
import java.util.logging.Logger;
public class Example {
private static Logger log =
Logger.getLogger("Example");
public static void main(String[] args) {
Example ob = new Example();
log.info("Creating threads");
SimpleThread one = new SimpleThread("one", ob,2);
SimpleThread two = new SimpleThread("two", ob,2);
one.start();
two.start();
}
public synchronized void firstWaitingRoom(
int sec, String name)
{ log.info(name+"..entering.");
try{
Thread.sleep(sec*1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
log.info(name+"..exiting.");
}
public synchronized void secondWaitingRoom(
int sec, String name) {
log.info(name+"..entering.");
try{
Thread.sleep(sec*1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
log.info(name+"..exiting.");
} |
public class SimpleThread extends Thread {
private String name;
private Example object;
private int delay;
public SimpleThread(String n,Example obj,int del){
name = n;
object = obj;
delay = del;
}
public void run() {
double y = Math.random();
int x = (int) (y * 2); // restituisce 1 o 0 in base al valore random
if(x==0)
object.firstWaitingRoom(delay, name);
else//x==1
object.secondWaitingRoom(delay, name);
}
} |
-
Abbiamo 2 thread che cercano di accedere
- a 2 metodi
- a volte allo stesso metodo, contemporaneamente.
-
I metodi possono essere di istanza o statici.
-
I metodi possono essere sincronizzati o non sincronizzati.
-
Un metodo può essere invocato da uno solo dei thread od entrambi.
-
Per ogni combinazione si dovrebbe verificare il comportamento e capirlo completamente.
- Supponiamo debbano accedere alla stessa sala d'attesa
- I 2 metodi sono sincronizzati
| Good | Bad |
|---|---|
public synchronized void firstWaitingRoom(
int sec, String name)
{ log.info(name+"..entering.");
try{
Thread.sleep(sec*1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
log.info(name+"..exiting.");
}
public synchronized void secondWaitingRoom(
int sec, String name) {
log.info(name+"..entering.");
try{
Thread.sleep(sec*1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
log.info(name+"..exiting.");
} |
- Supponiamo debbano accedere a 2 sale d'attesa diverse
- I 2 metodi sono sincronizzati
| Good | Bad |
|---|---|
public synchronized void firstWaitingRoom(
int sec, String name)
{ log.info(name+"..entering.");
try{
Thread.sleep(sec*1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
log.info(name+"..exiting.");
}
public synchronized void secondWaitingRoom(
int sec, String name) {
log.info(name+"..entering.");
try{
Thread.sleep(sec*1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
log.info(name+"..exiting.");
} |
- Supponiamo debbano accedere a 2 sale d'attesa diverse
- Solo 1 metodo è sincronizzato
| Good | Bad |
|---|---|
public synchronized void firstWaitingRoom(
int sec, String name)
{ log.info(name+"..entering.");
try{
Thread.sleep(sec*1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
log.info(name+"..exiting.");
}
public void secondWaitingRoom(
int sec, String name) {
log.info(name+"..entering.");
try{
Thread.sleep(sec*1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
log.info(name+"..exiting.");
} |
- non vi è mutua esclusione |
- Abbiamo un array ""grande"" (10mln di elementi)
- ogni inizializzazione deve avvenire 10000 volte!
public class ArrayInit extends Thread{
public static int[] data;
public static final int SIZE = 10000000; //10 milioni di elementi
public static void main(String[] args){
data = new int[SIZE];
long begin, end;
int j;
begin = System.currentTimeMillis();
for(int i = 0; i < SIZE; i++){
for(j=0; j < 10000; j++)
data[i] = i;
}
end = System.currentTimeMillis();
System.out.println("Time:"+ (end - begin) + "ms");
}//end main
}//end class- Abbiamo eseguito 'a cascata' 10000 volte l'assegnazione.
- Tempo: 2246ms
idea: Inizializzazione Concorrente
- Dividiamo invecee l’Array in blocchi, precisamente in SIZE/nThread. Dovremo passare al costruttore la posizione di partenza start e quanti elementi vanno inizializzati dim.
- Il thread main attende che tutti terminino con una join().
public class EffInit extends Thread {
private int start;
private int dim;
public static int[] data;
public static final int SIZE = 10000000;
public static final int MAX_THR = 8;
public EffInit(int start, int size){
this.start = start;
this.dim = size;
}
public void run(){
int j;
for(int i = 0; i <this.dim; i++){
for(j=0; j < 10000; j++)
data[this.start + i] = i;
}
}
public static void main(String[] args){
data = new int[SIZE];
long begin, end;
int start, j;
EffInit[] threads;
for(int numThread = 1; numThread <= MAX_THR; numThread++){
begin = System.currentTimeMillis();
start = 0;
threads = new EffInit[numThread];
for(j = 0; j < numThread; j++){
threads[j] = new EffInit(start, SIZE / numThread);
threads[j].start();
start += SIZE / numThread;
}//endfor
for(j = 0; j < numThread; j++){ //intanto il main attente che terminino
try{
threads[j].join();
}catch(InterruptedException e) {
e.printStackTrace();
}
end = System.currentTimeMillis();
System.out.println(numThread +"Thread(s): "+ (end - begin) +"ms");
}
}- È difficile determinare l'efficienza rispetto al metodo classico, in quanto sono presenti I/O Bound, CPU Bound, core reali, buffers, hyperthreading...
- ma è preferibile questa versione.
- È un Design Pattern della Programmazione ad Oggetti.
- Restringe l'istanziazione da parte di una classe ad 1 oggetto.
- Viene impiegato per le memorizzazioni degli stati: Printer, File, Resource Manager…
- Lazy Allocation: la allocazione avviene solo quando è utilizzato la prima volta.
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null) { //se non è stato creato
instance = new Singleton(); //crealo
}
return instance; //restituiscilo
}
}Se sono presenti più Thread dobbiamo usare synchronized
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null) { //se non è stato creato
instance = new Singleton(); //crealo
}
return instance; //restituiscilo
}
}- Cosa accade se avessimo più thread e chiamassi la classe Singleton quando ancora deve essere creata l'istanza?
- Interleaving! più singleton generati col codice sopra!
- Vediamo l'approccio qui sotto...
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() { // aggiungiamo synchronized
if(instance == null) { //se non è stato creato
instance = new Singleton(); //crealo
}
return instance; //restituiscilo
}
}- Le performance del codice sopra però non sono delle migliori. Anche se ora funziona.
- Eliminiamo l’invocazione sincronizzata del metodo e rendiamo sincronizzata solo la creazione del nuovo singleton.
- NON VA BENE! Se A e B entrano insieme nella sezione critica, prima A restituisce il primo singleton, poi B entra e ne crea uno nuovo, sovrascrivendolo. Dobbiamo effettuare ancora un ulteriore controllo e non è garantito che funzioni!
- Il codice d'esempio della procedura descritta è fornito qui sotto.
class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null) { //se non è stato creato
synchronized(Singleton.class) {
if(instance == null) //controlliamo che la situazione non sia cambiata
instance = new Singleton(); //crealo
}
}
return instance; //restituiscilo
}
}- Instance deve essere volatile.
- LazyHolder è inizializzata dalla JVM solo quando serve (alla prima getInstance()).
- Essendo un inizializzatore statico, viene eseguito una sola volta (al caricamento) e stabilisce una relazione happens-before tutte le altre operazioni sulla classe
| Esempio LazyHolder | Applicato al Singleton |
|---|---|
public class Something {
private Something() {}
private static class LazyHolder {
private static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
} |
public class Singleton {
// Private constructor to prevent external instantiation
private Singleton() {}
private static class SingletonHolder {
// Initialize the Singleton instance when the class is loaded
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
} |


