检查大小然后执行操作-ConcurrentLinkedDeque是否安全? - java

我只需要用新值替换Deque中的第一个值
如果大小将超过限制。我写了这段代码来解决它:

final class Some {
    final int buffer;
    final Deque<Operation> operations = new ConcurrentLinkedDeque<>();
    // constructors ommited;

    @Override
    public void register(final Operation operation) {
        if (this.operations.size() == this.buffer) {
            // remove the oldest operation
            this.operations.removeFirst();
        }
        // add new operation to the tail
        this.operations.addLast(operation);
    }

    @Override
    public void apply() {
        // take the fresh operation from tail and perform it
        this.operations.removeLast().perform();
    }
}

如您所见,我有两种方法可以修改Deque。我对此代码是否可以在多线程环境中正常运行感到怀疑。问题是:检查size()然后执行随后修改ConcurrentLinkedDeque的操作是否安全?我希望锁最少。因此,如果此代码行不通,那么我必须引入锁定,然后ConcurrentLinkedDeque()的使用就没有意义了。

final class Some {
    final int buffer;
    final Deque<Operation> operations = new LinkedList<>();
    final Lock lock = new ReentrantLock();
    // constructors ommited;

    @Override
    public void register(final Operation operation) {
        this.lock.lock();
        try {
            if (this.operations.size() == this.buffer) {
                // remove the oldest operation
                this.operations.removeFirst();
            }
            // add new operation to the tail
            this.operations.addLast(operation);
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void apply() {
        this.lock.lock();
        try {
            // take the fresh operation from tail and perform it
            this.operations.removeLast().perform();
        } finally {
            this.lock.unlock();
        }
    }
}

这是Lock的替代方法。那是实现我想要的唯一途径吗?我对尝试使用并发集合特别感兴趣。

参考方案

并发集合在进入内部状态时是线程安全的。换句话说,他们

允许多个线程同时读/写,而不必担心内部状态将被破坏
在其他线程修改集合时允许迭代和删除

但是,并非全部。我相信CopyOnWriteArrayListIterator不支持remove()操作

保证事前发生

意味着将由一个线程进行写操作-在随后的线程进行读取之前

但是,它们在外部方法调用之间不是线程安全的。当您调用一个方法时,它将获取所需的任何锁,但这些锁将在该方法返回时释放。如果您不小心,可能会导致先检查后再运行的比赛情况。查看您的代码

if (this.operations.size() == this.buffer) {
    this.operations.removeFirst();
}
this.operations.addLast(operation);

可能发生以下情况:

Thread-A检查大小条件,结果为false
Thread-A移动以添加新的Operation
Thread-A可以添加Operation之前,Thread-B检查大小条件,这也会导致false
Thread-B去添加新的Operation
Thread-A确实添加了新的Operation

不好了! Operation添加的Thread-A导致达到大小阈值

Thread-B,已经在if语句之后,添加了它的Operation,使得双端队列有太多的Operation

这就是为什么先检查后操作需要外部同步的原因,您在第二个示例中使用Lock进行了此操作。请注意,您也可以在synchronized上使用Deque块。

与您的问题无关:在第二个示例中,您在按住Operation.perform()的同时调用Lock。这意味着在执行Operation时,没有其他线程可以尝试向Deque添加另一个perform()。如果不希望这样,您可以像这样更改代码:

Operation op;

lock.lock();
try {
    op = deque.pollLast(); // poll won't throw exception if there is no element
} finally {
    lock.unlock();
}

if (op != null) {
    op.perform();
}

Java:线程池如何将线程映射到可运行对象 - java

试图绕过Java并发问题,并且很难理解线程池,线程以及它们正在执行的可运行“任务”之间的关系。如果我创建一个有10个线程的线程池,那么我是否必须将相同的任务传递给池中的每个线程,或者池化的线程实际上只是与任务无关的“工人无人机”可用于执行任何任务?无论哪种方式,Executor / ExecutorService如何将正确的任务分配给正确的线程? 参考方案 …

JAVA:字节码和二进制有什么区别? - java

java字节代码(已编译的语言,也称为目标代码)与机器代码(当前计算机的本机代码)之间有什么区别?我读过一些书,他们将字节码称为二进制指令,但我不知道为什么。 参考方案 字节码是独立于平台的,在Windows中运行的编译器编译的字节码仍将在linux / unix / mac中运行。机器代码是特定于平台的,如果在Windows x86中编译,则它将仅在Win…

java:继承 - java

有哪些替代继承的方法? java大神给出的解决方案 有效的Java:偏重于继承而不是继承。 (这实际上也来自“四人帮”)。他提出的理由是,如果扩展类未明确设计为继承,则继承会引起很多不正常的副作用。例如,对super.someMethod()的任何调用都可以引导您通过未知代码的意外路径。取而代之的是,持有对本来应该扩展的类的引用,然后委托给它。这是与Eric…

Java:BigInteger,如何通过OutputStream编写它 - java

我想将BigInteger写入文件。做这个的最好方式是什么。当然,我想从输入流中读取(使用程序,而不是人工)。我必须使用ObjectOutputStream还是有更好的方法?目的是使用尽可能少的字节。谢谢马丁 参考方案 Java序列化(ObjectOutputStream / ObjectInputStream)是将对象序列化为八位字节序列的一种通用方法。但…

Java DefaultSslContextFactory密钥库动态更新 - java

我有一个使用org.restlet.engine.ssl.DefaultSslContextFactory的现有应用程序和一个在服务器启动时加载的密钥库文件。我有另一个应用程序,该应用程序创建必须添加的证书服务器运行时动态地更新到密钥库文件。为此,我在代码中创建了证书和私钥,然后将其写入到目录。该目录由bash脚本监视,该脚本检查是否有新文件,如果出现,它将…