Странное поведение Java Deque в многопоточном окружении - PullRequest
0 голосов
/ 02 августа 2020

Я написал простой демонстрационный код, чтобы проверить, «как работает Daemon Thread». Но демонстрация демонстрирует другое странное поведение: я делаю Deque для хранения элемента с именем Event и разделяю его на два рабочих потока, один добавляю элемент в Deque. еще раз проверьте размер Deque и удалите элемент, который создается за 3 секунды a go. Здесь произошло странное: вызов size() Deque всегда возвращает 0. Я знаю, что ArrayDeque и LinkedDeque не являются поточно-ориентированными, но я могу исправить эту странную вещь следующим образом: 1 、 Измените инструменты Deque на ConcurrentLinkedDeque. 2 、 синхронизировал экземпляр двухсторонней очереди. 3. Перед запуском Cleaner поместите элемент в общую двухстороннюю очередь. 4 、 проверьте размер и распечатайте его. Все это работает хорошо, это очень странно, и я не знаю почему.

Вот демонстрационный код и моя среда выполнения:

java версия "1.8.0_141 «Java (TM) Среда выполнения SE (сборка 1.8.0_141-b15) Java 64-разрядная серверная виртуальная машина HotSpot (TM) (сборка 25.141-b15, смешанный режим)

MacBook Pro (13 дюймов, 2016 г., два порта Thunderbolt 3) MacOS Catalina 10.15.6

public class CleanerTask extends Thread {

    private transient Deque<Event> deque;

    private AtomicInteger doRemoveTimes = new AtomicInteger(0);

    public AtomicInteger getDoRemoveTimes() {
        return doRemoveTimes;
    }

    public CleanerTask(Deque<Event> deque) {
        this.deque = deque;
        setDaemon(true);
    }

    @Override
    public void run() {
        //System.out.println("Cleaner: watch deque " + deque);
        while (true) {
            clean();
        }
    }

    private void clean() {
        //fix 2
        /*synchronized (deque) {
            if(deque.size() == 0) {
                return;
            }
        }*/
        if (deque.size() == 0) {
            //System.out.println("Cleaner: deque's size:" + deque.size());//fix 3
            return;
        }
        int removes = 0;
        int beforeNext;
        do {
            beforeNext = removes;
            Event event = deque.getLast();
            if (Duration.between(event.getTime(), LocalTime.now()).getSeconds() > 3) {
                deque.removeLast();
                System.out.println(event + " is removed");
                removes++;
            }
        } while (removes > beforeNext && deque.size() > 0);
        if (removes > 0) {
            doRemoveTimes.addAndGet(removes);
            System.out.printf("Cleaner: cleaned %d, remained %d\n", removes, deque.size());
        }
    }
}
public class WriterTask implements Runnable {

    private Deque<Event> deque;

    public WriterTask(Deque<Event> deque) {
        this.deque = deque;
    }

    @Override
    public void run() {
        System.out.println(LocalTime.now() + "-" + Thread.currentThread().getId() + ": start write event to the deque: " + deque);
        for(int i = 0; i < 10; i++) {
            Event event = new Event("event generated by " + Thread.currentThread().getId());
            deque.addFirst(event);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(LocalTime.now() + "-" + Thread.currentThread().getId() + ": finished");
    }
}
public class DaemonCleanMain {

    public static void main(String[] args) {
        //Deque<Event> deque = new ConcurrentLinkedDeque<>();//fix 1
        Deque<Event> deque = new ArrayDeque<>();
        WriterTask writer = new WriterTask(deque);
        int i = args.length > 0 ? Integer.parseInt(args[0]) : 1;
        while (i > 0) {
            Thread thread = new Thread(writer);
            thread.start();
            i--;
        }
        //fix 4
       /* try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        CleanerTask cleaner = new CleanerTask(deque);
        cleaner.start();
        while (true) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("size of deque: " + deque.size());
            System.out.println("Cleaner is work? " + cleaner.getDoRemoveTimes());
        }
    }
}
public class Event {

    public Event(String name) {
        this.name = name;
        this.time = LocalTime.now();
    }

    public Event(String name, LocalTime time) {
        this.name = name;
        this.time = time;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LocalTime getTime() {
        return time;
    }

    public void setTime(LocalTime time) {
        this.time = time;
    }

    private String name;

    private LocalTime time;

    @Override
    public String toString() {
        return "Event{" +
                "name='" + name + '\'' +
                ", time=" + time +
                '}';
    }
}

Вопрос: исправление 3 и 4 , меня очень удивляет, потому что я считаю это очень странным методом исправления. И, хотя ArrayDeque не является потокобезопасным, но когда Writer остановлен, Cleaner по-прежнему получает size () return 0, когда на самом деле сейчас выполняется только один поток (кроме основного), он работает как двухсторонний к Очистителю является окончательным и неизменным.

1 Ответ

1 голос
/ 02 августа 2020

Одна любопытная особенность параллелизма - это модель памяти. Если вы не синхронизируете, два потока могут иметь разные «копии» или «представления» данных. Вот почему вы видите размер 0. Это противоречит интуиции, поскольку вы можете подумать, что они указывают на одно и то же, и это не так.

Здесь у вас есть более подробная информация: http://tutorials.jenkov.com/java-concurrency/java-memory-model.html

Хорошая новость в том, что вы знаете, как ее решить!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...