多线程环境中javadeque的奇怪行为

多线程环境中javadeque的奇怪行为,java,multithreading,Java,Multithreading,我编写了一个简单的演示代码来测试“守护进程线程如何工作”。但演示显示了另一种奇怪的行为: 我制作一个Deque来保存名为Event的元素,并为两个工作线程共享它,一个线程将元素添加到Deque。再次检查Deque的大小并移除3秒前创建的元素。奇怪的是,调用Deque的size()总是返回0。我知道ArrayDeque和linkedeque不是线程安全的,但我可以解决这样的奇怪问题: 1、 将Deque的实现更改为ConcurrentLinkedque。 2、 同步了deque实例。 3、 在初始

我编写了一个简单的演示代码来测试“守护进程线程如何工作”。但演示显示了另一种奇怪的行为: 我制作一个
Deque
来保存名为Event的元素,并为两个工作线程共享它,一个线程将元素添加到Deque。再次检查Deque的大小并移除3秒前创建的元素。奇怪的是,调用Deque的
size()
总是返回0。我知道
ArrayDeque
linkedeque
不是线程安全的,但我可以解决这样的奇怪问题: 1、 将Deque的实现更改为ConcurrentLinkedque。 2、 同步了deque实例。 3、 在初始化清理器之前,将一个元素放入共享deque。 4、 检查尺寸,然后打印出来。 所有这些都很有效,这很奇怪,我不知道为什么

以下是演示代码和我的运行时环境:

java版本“1.8.0_141” Java(TM)SE运行时环境(build 1.8.0_141-b15) Java HotSpot(TM)64位服务器虚拟机(构建25.141-b15,混合模式)

MacBook Pro(13英寸,2016年,两个Thunderbolt 3端口) MacOS Catalina 10.15.6


问题是:3和4的修正,让我非常惊讶,因为我认为这是一种非常奇怪的修正方法。而且,虽然ArrayQue不是线程安全的,但是当写入程序停止时,清理程序仍然会得到size()返回0,当实际只有一个线程正在运行时(主线程除外),它的工作原理就像清理程序的deque是最终的和不可变的。

关于并发性的一个奇怪的事情是内存模型。如果不同步,两个线程可能有不同的数据“副本”或“视图”。所以,这就是为什么您将大小视为0。这与直觉相反,因为你可以认为它们指向同一个东西,但事实并非如此

这里有更详细的信息:


好消息是你知道如何解决它

另一个修复方法:如果在Cleaner中放置调试器,它将很好地工作(我忘记了事件类,但它很简单,只有两个字段,一个是
String
type name,另一个是
LocalTime
type time for create time。因此,您观察到,当被多个线程访问时,被记录为线程不安全的类是不可靠的,除非您自己强制执行线程安全。您对此感到惊讶吗你已经知道,
ArrayDeque
不是线程安全的,那么问题是什么呢?3和4修复,让我非常惊讶。因为我认为这是一种非常奇怪的修复方法。虽然,
ArrayDeque
不是线程安全的,但是当写入程序停止时,清洁器仍然会得到大小()返回0,此时实际只有一个线程正在运行(主线程除外)。非常感谢,这非常令人惊讶。我一直认为共享数据是相同的。但实际不是。我将阅读此内容,再次感谢。
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 +
                '}';
    }
}