#java #multithreading #volatile
#java #многопоточность #изменчивый
Вопрос:
Когда я присваиваю новое значение переменной, оно не меняется после start(), однако после использования join() оно меняется. Почему это происходит, и в этом случае int a должен быть изменчивым или нет?
class SampleThread extends Thread {
private static int a = 0;
@Override
public void run() {
a = 3;
}
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(new SampleThread());
t2.start();
System.out.println(a);
}
}
Комментарии:
1. Почему вы ожидаете, что новый поток немедленно получит управление до того, как основной поток продолжит и выполнит
println
? Нет никакой гарантии в порядке отправки потоков.2. Это происходит, но основной поток продолжается
System.out.println(a);
до того, как изменится второй потокa
(поэтому изменение произойдет после печати). Чтобы увеличить вероятность обновления второго потокаa
до того, как основной поток его напечатает, заставьте этот основной поток немного подождать перед вызовомprintln
. Вы можете сделать это, вызвав что-то вродеThread.sleep(1000);
предыдущегоSystem.out.println(a);
.3. Если должно выполняться 2 потока, то может быть, что перед завершением работы JVM выполняется только 1 поток (основной поток); Чтобы предотвратить это, мы могли бы использовать join(), чтобы JVM ожидала завершения 2-го потока перед завершением. Я прав?
4. Если хотя бы один поток, не являющийся демоном, все еще активен, JVM не завершится. Поскольку ваш
t2
поток не был настроен как демон, ваше приложение завершит работу только тогда, когда основной поток завершится и когдаt2
завершится..join
используется, когда вы хотите заставить поток, вызывающий этот метод, ждать, пока другой поток завершит свою работу. В вашем случае, если вы вызываетеt2.join()
insidemain
, основной поток будет ждать, покаt2
завершит свою работу, и только после этого продолжит (но в этом случае это сводит на нет цель даже запуска отдельного потока).5. Если вы явно
.join()
включаетеt2
, вам не нужна volatile — поскольку присоединение к потоку устанавливает предшествующее ребро.
Ответ №1:
чтобы увидеть, что происходит, попробуйте следующее:
...
@Override
public void run() {
System.out.println("start of Thread");
a = 3;
System.out.println("end of Thread");
}
...
run
изменен только метод, остальной код без изменений
Ответ №2:
Да, ему нужна изменчивость.
В каждом потоке есть злая монета. Поток переворачивает монету всякий раз, когда он читает или записывает поле: Heads, и поток использует свою собственную локальную (для потока) копию этого; при записи это обновление просто не отражается на всех других потоках, которые все равно будут видеть «старое» значение, а при чтении,та же сделка: читает все, что у него было, даже если другие потоки уже обновили его. Даже если они сделали это час назад. Хвосты, и это обновляет представления других потоков об этой вещи и не будет использовать локальную копию.
Монета злая: это не честная монета. Это будет работать каждый раз сегодня, и каждый раз завтра, и каждый раз во время набора тестов, и всю эту неделю вы будете использовать его для первых пользователей. И затем, как только приходит этот большой клиент, и вы даете демонстрацию? Он каждый раз переворачивается, чтобы сломать ваше приложение. Такое зло.
Итак, вы должны исключить все подбрасывания монет или, по крайней мере, убедиться, что результат подбрасывания монеты никак не влияет на ваше приложение.
Способ сделать это — установить предварительные отношения. Между любыми 2 строками кода Java, выполняемыми виртуальной машиной, существует набор правил, определяющих, имеют ли эти 2 строки такую взаимосвязь: гарантируется, что одна будет выполняться после другой. Дело не в том, изменились ли они (временные метки времени их запуска совершенно не важны), а в том, определяет ли модель памяти Java, что такая связь существует.
Если да, то монета не переворачивается: все, что сделала «строка, которая была до этого в соответствии с JMM», определенно видно в строке, которая была после. Но если в JMM явно не указано, что эта связь существует, монета переворачивается, и вы проигрываете.
Один тривиальный корабль отношений «предшествует» находится в одном потоке: x = 5; System.out.println(x);
тривиально имеет такие отношения; они запускались в одном потоке, один за другим. Это халява.
Но между потоками, о боже. Вам нужен synchronized
, volatile
, или код вызова, который выполняет эти действия внутри или имеет другие механизмы для его обеспечения (совет: в пакете java.util.concurrent есть много отличных вещей, и, как следует из названия, он, как правило, потокобезопасен очень эффективными способами. Например, an AtomicInteger
почти всегда намного лучше, чем a volatile int
, и может выполнять гораздо больше, например, операции CAS, которые не могут выполнять изменяемые целые числа.
Комментарии:
1. Если должно выполняться 2 потока, то может быть, что перед завершением работы JVM выполняется только 1 поток (основной поток); Чтобы предотвратить это, мы могли бы использовать join(), чтобы JVM ожидала завершения 2-го потока перед завершением. Я прав?
2. нет, виртуальная машина не завершается, пока не будут выполнены все потоки *. *) если вы установите флаг ‘daemon’ в потоке, это не будет учитываться. Здесь нет необходимости присоединяться. Обратите внимание, что объединение — это один из способов установить предшествующее. все инструкции, выполняемые потоком X, предшествуют разрешению
X.join()
. Таким образом, после объединения переменная гарантированно обновляется. Вам нужно прочитать JMM и полный список правил CBCA или отказаться от обмена данными между потоками через поля.
Ответ №3:
Если вы ожидаете, что что-то в разных потоках будет происходить в определенном порядке — в данном случае это a = 2
выполняется перед `System.out.println (a)’ — тогда вам нужно написать код, чтобы этот порядок выполнялся.
В этом тривиальном случае, когда не выполняется никакой реальной работы, почти все, что вы можете сделать, делает использование потоков бессмысленным. Основной поток может «присоединиться» к потоку, для которого установлено a
значение 2, но тогда все, чего вы достигли, — это дорогостоящий способ выполнения кода, который может быть выполнен в одном потоке.
Комментарии:
1. Но это, очевидно, не для реальных приложений, а для учебных целей. Итак, то, что вы сказали, действительно не имеет значения.
Ответ №4:
основной поток должен дождаться выполнения другого. Одним из решений является использование join()
class SampleThread extends Thread {
private static int a = 0;
@Override
public void run() {
a = 3;
}
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(new SampleThread());
t2.start();
// wait for completion of t2
t2.join()
System.out.println(a);
}
}
Комментарии:
1. Но если значение не присваивается внутри t2, то почему код в t2 немедленно запускается? И завершается ли JVM, когда активен поток deamon?