#java #multithreading
#java #многопоточность
Вопрос:
Вот мой код:
public class MyRunnableClass implements Runnable {
static int x = 25;
int y = 0;
private static final Object sharedLock = new Object();
@Override
public void run() {
while(x>0){
someMethod();
}
}
public synchronized void someMethod(){
synchronized (sharedLock){
x--;
y ;
}
}
}
и тестовый класс:
public class MyRunnableClassTest {
public static void main(String[] args) throws InterruptedException {
MyRunnableClass aa = new MyRunnableClass();
MyRunnableClass bb = new MyRunnableClass();
Thread a = new Thread(aa);
Thread b = new Thread(bb);
a.start();
b.start();
a.join();
b.join();
System.out.println(aa.y bb.y);
}
}
Когда я запускаю этот код как есть, я вижу вывод 25, что нормально, но когда x равно 250, я вижу 251.. Почему? Почему не 250?
Комментарии:
1. Если вы запускаете снова и снова с x = 25, вы также увидите тот же эффект, потому что у вас состояние гонки.
2. Создание x volatile не имеет никакого значения @user3580294
3. Ну, тогда не бери в голову… Но вопрос о другом значении
x
остается в силе.
Ответ №1:
Вы должны расширить synchronized
область видимости, чтобы это также охватывало операцию чтения на x
:
@Override
public void run() {
for (;;) {
synchronized (sharedObject) {
if (x <= 0) break;
someMethod();
}
}
}
Ответ №2:
Совпадение. То же самое может произойти с 25
, как и с любым другим числом.
Например, во время выполнения
while(x>0){
someMethod();
}
который не синхронизирован, после множества циклов давайте примем x
равным 1. Первый поток начинает выполнение итерации (входит в тело), затем потоки переключаются, второй поток видит, что x
равно 1, поэтому также входит в тело цикла. Оба будут увеличивать свое количество, и их сумма будет на единицу больше исходного x
значения.
Это условие гонки, и вам просто легче увидеть последствия с большими числами.
Комментарии:
1. Комментарии удалены? Почему?
2. @KorayTugay Я отредактировал свой ответ, чтобы отразить то, что было сказано.
Ответ №3:
Когда вы делаете:
while(x>0){
someMethod();
}
Допустим, x = 1 и:
Поток A оценивает x > 0 как true и входит в цикл. Допустим, поток A прерывается перед выполнением следующей строки. Поток B также оценит x > 0 как true и войдет в цикл.
Оба будут уменьшать x один за другим и увеличивать их y.
Чтобы решить эту проблему, проверка на x > 0 также должна быть заблокирована.
Пример:
public class MyRunnableClass implements Runnable {
static int x = 25;
int y = 0;
private static final Object sharedLock = new Object();
@Override
public void run() {
while(x>0){
someMethod();
}
}
public synchronized void someMethod(){
synchronized (sharedLock){
if(x > 0){
x--;
y ;
}
}
}
}
Ответ №4:
Иногда и поток a, и поток b могут вызывать someMethod(), потому что x равно 1. Один поток блокирует sharedLock, делает x равным 0, y равным 250, а затем освобождает sharedLock, после чего другой поток вызывает someMethod() и делает y равным 251, а x равным -1.
Ответ №5:
Вы также могли бы решить эту проблему с помощью AtomicInteger
:
import java.util.concurrent.atomic.AtomicInteger;
public class MyRunnableClass implements Runnable {
private static final AtomicInteger xHolder = new AtomicInteger(25);
int y = 0;
@Override
public void run() {
while (xHolder.decrementAndGet() >= 0) {
y ;
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnableClass aa = new MyRunnableClass();
MyRunnableClass bb = new MyRunnableClass();
Thread a = new Thread(aa);
Thread b = new Thread(bb);
a.start();
b.start();
a.join();
b.join();
System.out.println(aa.y bb.y);
}
}
Или с некоторым расширенным параллелизмом для тестирования:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
public class MyRunnableClass implements Callable<Integer> {
private static final AtomicInteger xHolder = new AtomicInteger(250000);
int y = 0;
@Override
public Integer call() throws Exception {
while (xHolder.decrementAndGet() >= 0) {
y ;
}
System.out.println(Thread.currentThread().getName() " returns " y);
return y;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorCompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(
executorService);
int parallelism = 5;
for (int i = 0; i < parallelism; i) {
completionService.submit(new MyRunnableClass());
} // for
int ySum = 0;
for (int j = 0; j < parallelism; j) {
Future<Integer> future = completionService.take();
ySum = future.get();
} // for
System.out.println(ySum);
executorService.shutdown();
}
}
Вывод:
pool-1-thread-3 returns 26619
pool-1-thread-5 returns 0
pool-1-thread-1 returns 104302
pool-1-thread-2 returns 95981
pool-1-thread-4 returns 23098
250000