W przypadku, gdy 2 różne wątki operują np. za pośrednictwem jakiejś metody na tej samej zmiennej, czy jakimś obiekcie, pojawia się problem z synchronizacją. Mogą powstawać niepoprawne obiekty, zmienne mogą nabyć nieoczekiwanych wartości itd.. Idealnie obrazuje to poniższy przykład.
Dwa różne wątki wykonują te same operacje, przelewając pieniądze z jednego konta, na drugie. Generalnie saldo obrazujące ogólną ilość pieniędzy nie powinno być różne od wartości 4000, która jest sumą pieniędzy z dwóch kont. W przykładzie na kontach może być mniej niż 0 złotówek.
package threadstest;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Bank {
private int konta[] = new int[2];
private int saldo;
private static int nrOperacji;
public Bank() {
konta[0] = 2000;
konta[1] = 2000;
saldo = konta[0] + konta[1];
}
public void przelej(int i, int j, int kwota) {
nrOperacji++;
konta[i] -= kwota;
konta[j] += kwota;
saldo = konta[i] + konta[j];
System.out.println("Transfer z konta [" + i + "] (" + konta[i] +") na [" + j + "] (" + konta[j] + "). Przelana kwota: " + kwota + " zł. Saldo wynosi: "+ saldo + " zł. Operacja nr " + nrOperacji + ".");
}
}
public class ThreadsTest {
public static void main(String[] args) {
Bank bank = new Bank();
Random random = new Random();
while(true) {
Runnable run = new Runnable() {
public void run() {
int test = random.nextInt(2); // Losuje, z którego konta ma zostać wykonany przelew.
if (test == 1) {
bank.przelej(0, 1, random.nextInt(70));
} else {
bank.przelej(1, 0, random.nextInt(70));
}
}
};
Thread thread = new Thread(run);
Thread thread2 = new Thread(run);
thread.start();
thread2.start();
}
}
}
- package threadstest;
- import java.util.Random;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
-
- class Bank {
- private int konta[] = new int[2];
- private int saldo;
- private static int nrOperacji;
-
- public Bank() {
- konta[0] = 2000;
- konta[1] = 2000;
- saldo = konta[0] + konta[1];
- }
-
- public void przelej(int i, int j, int kwota) {
- nrOperacji++;
- konta[i] -= kwota;
- konta[j] += kwota;
- saldo = konta[i] + konta[j];
- System.out.println("Transfer z konta [" + i + "] (" + konta[i] +") na [" + j + "] (" + konta[j] + "). Przelana kwota: " + kwota + " zł. Saldo wynosi: "+ saldo + " zł. Operacja nr " + nrOperacji + ".");
- }
- }
-
- public class ThreadsTest {
-
- public static void main(String[] args) {
- Bank bank = new Bank();
- Random random = new Random();
-
- while(true) {
- Runnable run = new Runnable() {
- public void run() {
- int test = random.nextInt(2); // Losuje, z którego konta ma zostać wykonany przelew.
- if (test == 1) {
- bank.przelej(0, 1, random.nextInt(70));
- } else {
- bank.przelej(1, 0, random.nextInt(70));
- }
- }
- };
-
- Thread thread = new Thread(run);
- Thread thread2 = new Thread(run);
-
- thread.start();
- thread2.start();
- }
- }
-
- }
package threadstest;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Bank {
private int konta[] = new int[2];
private int saldo;
private static int nrOperacji;
public Bank() {
konta[0] = 2000;
konta[1] = 2000;
saldo = konta[0] + konta[1];
}
public void przelej(int i, int j, int kwota) {
nrOperacji++;
konta[i] -= kwota;
konta[j] += kwota;
saldo = konta[i] + konta[j];
System.out.println("Transfer z konta [" + i + "] (" + konta[i] +") na [" + j + "] (" + konta[j] + "). Przelana kwota: " + kwota + " zł. Saldo wynosi: "+ saldo + " zł. Operacja nr " + nrOperacji + ".");
}
}
public class ThreadsTest {
public static void main(String[] args) {
Bank bank = new Bank();
Random random = new Random();
while(true) {
Runnable run = new Runnable() {
public void run() {
int test = random.nextInt(2); // Losuje, z którego konta ma zostać wykonany przelew.
if (test == 1) {
bank.przelej(0, 1, random.nextInt(70));
} else {
bank.przelej(1, 0, random.nextInt(70));
}
}
};
Thread thread = new Thread(run);
Thread thread2 = new Thread(run);
thread.start();
thread2.start();
}
}
}
Jednak w tym przypadku dzieją się rzeczy, które miejsca mieć nie powinny. Z powodu „stylu” w jakim działają wątki (krótko ujmując chwilę działa jeden, chwilę drugi – itd. aż nie wykonają swoich zadań), saldo w tym konkretnym przypadku zmienia (teoretycznie) niezmienialną wartość 4000 zł. W BARDZO dużym skrócie dzieje się tak dlatego, że jeden z wątków zostaje zatrzymany w celu ustąpienia innemu, nie kończąc tym samym swojego zadania do końca.
Wystarczy nawet kilkadziesiąt „transferów / przelewów”, by saldo zmieniło wartość. W tym przypadku widać, że nawet zmienna nrOperacji się „sypie”:
Transfer z konta [1] (1815) na [0] (2185). Przelana kwota: 14 zł. Saldo wynosi: 4000 zł. Operacja nr 63.
Transfer z konta [1] (1809) na [0] (2191). Przelana kwota: 57 zł. Saldo wynosi: 4000 zł. Operacja nr 61.
Transfer z konta [1] (1621) na [0] (2416). Przelana kwota: 36 zł. Saldo wynosi: 4037 zł. Operacja nr 72.
Transfer z konta [0] (2380) na [1] (1657). Przelana kwota: 20 zł. Saldo wynosi: 4037 zł. Operacja nr 71.
Jedną z możliwości naprawienia tego problemu jest wykorzystanie obiektu klasy Lock i jego metod lock i unlock. Taka opcja pozwala na dostęp do metody tylko jednemu wątkowi w tym samym czasie. Wątek musi zakończyć pracę z metodą, by kolejny mógł ją rozpocząć:
package threadstest;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Bank {
private int konta[] = new int[2];
private int saldo;
private static int nrOperacji = 0;
public Bank() {
konta[0] = 2000;
konta[1] = 2000;
saldo = konta[0] + konta[1];
}
private Lock przelejLock = new ReentrantLock(); // odpowiada za nałożenie blokady
public void przelej(int i, int j, int kwota) {
przelejLock.lock(); // blokada włączona zaraz przed rozpoczęciem działania metody
try {
nrOperacji++;
konta[i] -= kwota;
konta[j] += kwota;
saldo = konta[i] + konta[j];
System.out.println("Transfer z konta [" + i + "] (" + konta[i] +") na [" + j + "] (" + konta[j] + "). Przelana kwota: " + kwota + " zł. Saldo wynosi: "+ saldo + " zł. Operacja nr " + nrOperacji + ".");
} finally {
przelejLock.unlock(); // blokada zdjęta po zakończeniu zadań z bloku try
}
}
}
public class ThreadsTest {
public static void main(String[] args) {
Bank bank = new Bank();
Random random = new Random();
while(true) {
Runnable run = new Runnable() {
public void run() {
int test = random.nextInt(2); //losuje z którego konta ma zostać wykonany przelew
if(test == 1) {
bank.przelej(0, 1, random.nextInt(70));
} else {
bank.przelej(1, 0, random.nextInt(70));
}
}
};
Thread thread = new Thread(run);
Thread thread2 = new Thread(run);
thread.start();
thread2.start();
}
}
}
- package threadstest;
-
- import java.util.Random;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
-
- class Bank {
- private int konta[] = new int[2];
- private int saldo;
- private static int nrOperacji = 0;
-
- public Bank() {
- konta[0] = 2000;
- konta[1] = 2000;
- saldo = konta[0] + konta[1];
- }
-
- private Lock przelejLock = new ReentrantLock(); // odpowiada za nałożenie blokady
-
- public void przelej(int i, int j, int kwota) {
- przelejLock.lock(); // blokada włączona zaraz przed rozpoczęciem działania metody
- try {
- nrOperacji++;
- konta[i] -= kwota;
- konta[j] += kwota;
- saldo = konta[i] + konta[j];
- System.out.println("Transfer z konta [" + i + "] (" + konta[i] +") na [" + j + "] (" + konta[j] + "). Przelana kwota: " + kwota + " zł. Saldo wynosi: "+ saldo + " zł. Operacja nr " + nrOperacji + ".");
- } finally {
- przelejLock.unlock(); // blokada zdjęta po zakończeniu zadań z bloku try
- }
- }
- }
-
- public class ThreadsTest {
-
- public static void main(String[] args) {
- Bank bank = new Bank();
- Random random = new Random();
-
- while(true) {
- Runnable run = new Runnable() {
- public void run() {
- int test = random.nextInt(2); //losuje z którego konta ma zostać wykonany przelew
- if(test == 1) {
- bank.przelej(0, 1, random.nextInt(70));
- } else {
- bank.przelej(1, 0, random.nextInt(70));
- }
- }
- };
-
- Thread thread = new Thread(run);
- Thread thread2 = new Thread(run);
-
- thread.start();
- thread2.start();
- }
- }
-
- }
package threadstest;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Bank {
private int konta[] = new int[2];
private int saldo;
private static int nrOperacji = 0;
public Bank() {
konta[0] = 2000;
konta[1] = 2000;
saldo = konta[0] + konta[1];
}
private Lock przelejLock = new ReentrantLock(); // odpowiada za nałożenie blokady
public void przelej(int i, int j, int kwota) {
przelejLock.lock(); // blokada włączona zaraz przed rozpoczęciem działania metody
try {
nrOperacji++;
konta[i] -= kwota;
konta[j] += kwota;
saldo = konta[i] + konta[j];
System.out.println("Transfer z konta [" + i + "] (" + konta[i] +") na [" + j + "] (" + konta[j] + "). Przelana kwota: " + kwota + " zł. Saldo wynosi: "+ saldo + " zł. Operacja nr " + nrOperacji + ".");
} finally {
przelejLock.unlock(); // blokada zdjęta po zakończeniu zadań z bloku try
}
}
}
public class ThreadsTest {
public static void main(String[] args) {
Bank bank = new Bank();
Random random = new Random();
while(true) {
Runnable run = new Runnable() {
public void run() {
int test = random.nextInt(2); //losuje z którego konta ma zostać wykonany przelew
if(test == 1) {
bank.przelej(0, 1, random.nextInt(70));
} else {
bank.przelej(1, 0, random.nextInt(70));
}
}
};
Thread thread = new Thread(run);
Thread thread2 = new Thread(run);
thread.start();
thread2.start();
}
}
}
Post Views:
967