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();
}
捕获(忽略最终中断异常){