Java 将同步替换为原子+;低锁争用情况下的while循环

Java 将同步替换为原子+;低锁争用情况下的while循环,java,concurrency,java.util.concurrent,Java,Concurrency,Java.util.concurrent,我有两个必须在关键部分运行的功能: public synchronized void f1() { ... } public synchronized void f2() { ... } 假设行为如下所示: f1几乎从未被调用。实际上,在正常情况下,永远不会调用此方法。如果仍然调用了f1,它应该会很快返回 f2的调用率非常高。它很快就回来了 这些方法从不互相调用,也不存在可重入性 换言之,争议非常小。因此,当调用f2时,我们会有一些开销来获取锁,在99.9%的情况下,锁会立即被授予。我想知

我有两个必须在关键部分运行的功能:

public synchronized void f1() { ... }
public synchronized void f2() { ... }
假设行为如下所示:

  • f1
    几乎从未被调用。实际上,在正常情况下,永远不会调用此方法。如果仍然调用了
    f1
    ,它应该会很快返回
  • f2
    的调用率非常高。它很快就回来了
  • 这些方法从不互相调用,也不存在可重入性
换言之,争议非常小。因此,当调用
f2
时,我们会有一些开销来获取锁,在99.9%的情况下,锁会立即被授予。我想知道是否有办法避免这种开销

我提出了以下备选方案:

private final AtomicInteger lock = new AtomicInteger(0);

public void f1() {
    while (!lock.compareAndSet(0, 1)) {}

    try {
        ...
    } finally {
        lock.set(0);
    }
}

public void f2() {
    while (!lock.compareAndSet(0, 2)) {}

    try {
        ...
    } finally {
        lock.set(0);
    }
}
还有其他方法吗?
java.util.concurrent
包是否提供了一些本机功能

更新

虽然我的目的是提出一个一般性问题,但关于我的情况的一些信息:

f1
:如果由于某种原因,当前远程流损坏,例如由于超时,此方法将创建一个新的远程流。远程流可以被视为套接字连接,它使用从给定位置开始的远程队列:

private Stream stream;

public synchronized void f1() {
     final Stream stream = new Stream(...);

     if (this.stream != null) {
         stream.setPosition(this.stream.getPosition());
     }
     this.stream = stream;
     return stream;
}
f2
:此方法使流位置提前。这是一个简单的设置器:

public synchronized void f2(Long p) {
    stream.setPosition(p);
}
这里,
stream.setPosition(Long)
也作为普通setter实现:

public class Stream {
    private volatile Long position = 0;

    public void setPosition(Long position) {
        this.position = position;
    }
}
流中
,当前位置将定期异步发送到服务器。请注意,
不是我自己实现的


我的想法是如上所示引入比较和交换,并将
stream
标记为
volatile

您的示例没有达到您希望的效果。当使用锁时,实际上正在执行代码。试着这样做:

public void f1() {
    while (!lock.compareAndSet(0, 1)) {
    }

    try {
        ...
    } finally {
        lock.set(0);
    }
}

为了回答您的问题,我认为这不会比使用
同步方法快,而且这种方法更难阅读和理解。

另一种方法是使用时间戳锁,它的工作原理类似于修改计数。如果你有一个高的读写比,这是很好的工作


另一种方法是使用不可变对象,该对象通过原子引用存储状态。如果您具有非常高的读写比,则此操作非常有效。

从描述和示例代码中,我推断出以下几点:

  • Stream
    有自己的内部位置,您还可以从外部跟踪最近的
    位置。您可以将其用作一种“恢复点”:当您需要重新初始化流时,可以将其推进到此点

  • 最后一个已知的
    位置可能已过时;我假设这是基于您的断言,即流定期异步通知服务器其当前位置

  • 调用
    f1
    时,已知流处于不良状态

  • 函数
    f1
    f2
    访问相同的数据,并且可以同时运行。但是,无论是
    f1
    还是
    f2
    都不会同时针对自身运行。换句话说,除了少数情况下同时执行
    f1
    f2
    之外,几乎只有一个单线程程序

    [旁注:我的解决方案实际上并不关心
    f1
    是否与自身同时被调用;它只关心
    f2
    是否与自身同时被调用]

  • 如果其中任何一个是错误的,那么下面的解决方案就是错误的。见鬼,这可能是错误的,要么是因为遗漏了一些细节,要么是因为我犯了一个错误。编写低锁代码很难,这正是您应该避免它的原因,除非您观察到实际的性能问题

    static class Stream {
        private long position = 0L;
    
        void setPosition(long position) {
            this.position = position;
        }
    }
    
    final static class StreamInfo {
        final Stream stream = new Stream();
        volatile long resumePosition = -1;
    
        final void setPosition(final long position) {
            stream.setPosition(position);
            resumePosition = position;
        }
    }
    
    private final Object updateLock = new Object();
    private final AtomicReference<StreamInfo> currentInfo = new AtomicReference<>(new StreamInfo());
    
    void f1() {
        synchronized (updateLock) {
            final StreamInfo oldInfo = currentInfo.getAndSet(null);
            final StreamInfo newInfo = new StreamInfo();
    
            if (oldInfo != null && oldInfo.resumePosition > 0L) {
                newInfo.setPosition(oldInfo.resumePosition);
            }
    
            // Only `f2` can modify `currentInfo`, so update it last.
            currentInfo.set(newInfo);
    
            // The `f2` thread might be waiting for us, so wake them up.
            updateLock.notifyAll();
        }
    }
    
    void f2(final long newPosition) {
        while (true) {
            final StreamInfo s = acquireStream();
    
            s.setPosition(newPosition);
            s.resumePosition = newPosition;
    
            // Make sure the stream wasn't replaced while we worked.
            // If it was, run again with the new stream.
            if (acquireStream() == s) {
                break;
            }
        }
    }
    
    private StreamInfo acquireStream() {
        // Optimistic concurrency: hope we get a stream that's ready to go.
        // If we fail, branch off into a slower code path that waits for it.
        final StreamInfo s = currentInfo.get();
        return s != null ? s : acquireStreamSlow();
    }
    
    private StreamInfo acquireStreamSlow() {
        synchronized (updateLock) {
            while (true) {
                final StreamInfo s = currentInfo.get();
    
                if (s != null) {
                    return s;
                }
    
                try {
                    updateLock.wait();
                }
                catch (final InterruptedException ignored) {
                }
            }
        }
    }
    
    静态类流{
    私人长仓=0L;
    无效设置位置(长位置){
    这个位置=位置;
    }
    }
    最终静态类StreamInfo{
    最终流=新流();
    波动性长位置=-1;
    最终空隙设置位置(最终多头位置){
    流。设置位置(位置);
    恢复位置=位置;
    }
    }
    私有最终对象updateLock=新对象();
    private final AtomicReference currentInfo=新的AtomicReference(新的StreamInfo());
    void f1(){
    已同步(updateLock){
    最终流信息oldInfo=currentInfo.getAndSet(null);
    最终StreamInfo newInfo=新StreamInfo();
    如果(oldInfo!=null&&oldInfo.resumePosition>0L){
    newInfo.setPosition(oldInfo.resumePosition);
    }
    //只有'f2'可以修改'currentInfo',所以最后更新它。
    currentInfo.set(newInfo);
    //“f2”线程可能正在等我们,所以请唤醒他们。
    updateLock.notifyAll();
    }
    }
    空f2(最终长新位置){
    while(true){
    最终流信息s=acquireStream();
    s、 设置位置(新位置);
    s、 resumePosition=新职位;
    //确保在我们工作时没有更换该流。
    //如果是,请使用新流再次运行。
    如果(acquireStream()==s){
    打破
    }
    }
    }
    私有StreamInfo acquireStream(){
    //乐观并发:希望我们得到一条随时准备好的流。
    //如果我们失败了,分支到一个较慢的代码路径等待它。
    最终StreamInfo s=currentInfo.get();
    返回s!=null?s:acquireStreamSlow();
    }
    私有StreamInfo acquireStreamSlow(){
    已同步(updateLock){
    while(true){
    最终StreamInfo s=currentInfo.get();
    如果(s!=null){
    返回s;
    }
    试一试{
    updateLock.wait();
    }
    捕获(忽略最终中断异常){