Изменчивая переменная для операций чтения-записи в Java - PullRequest
0 голосов
/ 18 марта 2019

Я изучаю volatile и synchronized в Java и вижу, что synchronized используется для операций чтения-изменения-записи, таких как x++, а volatile для операций чтения-записи. И я хочу задать вам 2 вопроса. Как выглядит операция чтения-записи?

И для второго вопроса у меня есть этот код:

public class StopThread {
    private static volatile boolean stop;

    public static void main(String[] args) throws InterruptedException { 

        new Thread(new Runnable() {

            @Override
            public void run() {
                while (!stop) {
                    System.out.println("In while...");
                }
            }

        }).start();

        TimeUnit.SECONDS.sleep(1); 
        stop = true;
    }

}

И я не понимаю, почему это операция чтения-записи, потому что переменная останова будет изменена с false на true. Так разве это не операция чтения-изменения-записи? Спасибо!

1 Ответ

3 голосов
/ 02 апреля 2019

Оператор stop = true; не является операцией «чтение-запись», а только запись . Он вообще не читает старое значение переменной. Если предыдущее значение stop было true, оператор не имел никакого эффекта, не заметив разницу.

Операция «чтение-изменение-запись», также известная как операция «чтение-обновление-запись», подразумевает операцию, которая считывает предыдущее значение, вычисляет новое значение на его основе и записывает новое значение обратно в переменную. , Проблема с этой операцией, когда не используется специальная атомарная конструкция обновления, состоит в том, что к тому времени, когда выполняется запись, может произойти одновременное обновление, поэтому переменная перезаписывается вычисленным значением, которое основано на устаревшем предыдущем значении .

Для вашей переменной boolean операции «чтение-изменение-запись» могут выглядеть как

if(!stop) stop = true;

или

stop = !stop;

, но для первого варианта пропуск одновременного обновления не окажет большого влияния, поскольку оператор не имеет эффекта, если переменная уже true. Второй может пропустить обновления, если они выполняются одновременно, следовательно, не отражают правильное количество операций переворачивания, но использование одновременных обновлений boolean для более чем одного перехода состояния в целом подвержено ошибкам.

Операция «чтение-запись», т.е. без «изменения / обновления» между ними, будет операцией, которая считывает старое значение для последующего использования и записывает новое значение, не основанное на старом значении. Как

Type old = variable;
variable = newValue;
// use old here

, которые все еще будут подвержены потерянным обновлениям, если не будут сделаны атомарно. Таким образом, такая операция также требует больше, чем volative переменная. Например. AtomicInteger.getAndSet или VarHandle.getAndSet.

Итак, расширив свой пример до

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class StopThread {
    private static volatile boolean stop;

    public static void main(String[] args) throws InterruptedException { 

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(!stop) {
                    System.out.println("In while...");
                }
            }
        }).start();
        for(int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); 
                    boolean old = stop; // broken because
                    stop = true;        // not atomic
                    System.out.println(old? "other thread was faster": "sent stop signal");
                }
            }).start();
        }
    }
}

несколько потоков могут подумать, что отправили сигнал остановки.

Если вы исправите код

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;

public class StopThread {
    private static final AtomicBoolean stop = new AtomicBoolean();

    public static void main(String[] args) throws InterruptedException { 

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(!stop.get()) {
                    System.out.println("In while...");
                }
            }
        }).start();
        for(int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); 
                    boolean old = stop.getAndSet(true);
                    System.out.println(old? "other thread was faster": "sent stop signal");
                }
            }).start();
        }
    }
}

именно один поток возьмет на себя ответственность за отправку сигнала остановки.

...