在Java中,AtomicInteger compareAndSet()与synchronized关键字的性能如何?

我正在实现请求实例的FIFO队列(预分配的请求对象以提高速度),并在add方法上使用“synchronized”关键字开始。 该方法非常短(检查固定大小缓冲区中的空间,然后向数组添加值)。 使用visualVM看起来线程比我喜欢的更频繁地阻塞(“监视器”是精确的)。 所以我将代码转换为使用AtomicInteger值,例如跟踪当前大小,然后在while循环中使用compareAndSet()(因为AtomicInteger在内部为incrementAndGet()等方法执行)。 现在代码看起来要长一些。

我想知道的是使用synchronized和更短代码的性能开销与没有synchronized关键字的更长代码相比(因此永远不应该阻塞锁)。

这是使用synchronized关键字的旧get方法:

public synchronized Request get() { if (head == tail) { return null; } Request r = requests[head]; head = (head + 1) % requests.length; return r; } 

这是没有synchronized关键字的新get方法:

 public Request get() { while (true) { int current = size.get(); if (current <= 0) { return null; } if (size.compareAndSet(current, current - 1)) { break; } } while (true) { int current = head.get(); int nextHead = (current + 1) % requests.length; if (head.compareAndSet(current, nextHead)) { return requests[current]; } } } 

我的猜测是synchronized关键字更糟,因为锁定的风险(可能导致线程上下文切换等),即使代码较短。

谢谢!

我的猜测是synchronized关键字更糟,因为锁定的风险(可能导致线程上下文切换等)

是的,在一般情况下你是对的。 Java Concurrency in Practice在第15.3.2节中讨论了这一点:

[…]在高争用级别锁定往往优于primefaces变量,但在更现实的争用级别,primefaces变量优于锁定。 这是因为锁通过挂起线程来对争用作出反应,从而减少共享内存总线上的CPU使用率和同步流量。 (这类似于生产者 – 消费者设计中的阻塞生产者如何减少消费者的负担,从而让他们赶上来。)另一方面,通过primefaces变量,争用管理被推回到调用类。 与大多数基于CAS的算法一样, AtomicPseudoRandom通过立即再次尝试来对争用做出反应,这通常是正确的方法,但在高争用环境中只会产生更多争用。

在我们将AtomicPseudoRandom谴责为写入不良或primefaces变量作为与锁相比较差的选择之前,我们应该意识到图15.1中的争用程度是不切实际的高:没有真正的程序只会争用锁或primefaces变量。 在实践中,primefaces倾向于比锁更好地扩展,因为primefaces更有效地处理典型的争用水平。

在不同的争用水平下,锁和primefaces之间的性能逆转说明了各自的优缺点。 由于低到中等的争用,primefaces提供了更好的可扩展性; 在高争用的情况下,锁可提供更好的争用避免。 (基于CAS的算法在单CPU系统上也优于基于锁定的算法,因为CAS总是在单CPU系统上成功,除非在读 – 修改 – 写操作过程中线程被抢占的情况不太可能。 )

(在文中提到的数字上,图15.1显示,当争用率很高时,AtomicInteger和ReentrantLock的性能大致相等,而图15.2显示,在中等争用下,前者的表现优于后者2-3倍。)

更新:关于非阻塞算法

正如其他人所指出的那样,非阻塞算法虽然可能更快,但更复杂,因此更难以正确实现。 JCiA第15.4节的提示:

很好的非阻塞算法对于许多常见的数据结构是已知的,包括堆栈,队列,优先级队列和哈希表,尽管设计新的算法最好留给专家。

非阻塞算法比基于锁的算法复杂得多。 创建非阻塞算法的关键是弄清楚如何在保持数据一致性的同时将primefaces更改的范围限制为单个变量。 在诸如队列之类的链接集合类中,您有时可以将状态转换表示为对单个链接的更改,并使用AtomicReference来表示必须以primefaces方式更新的每个链接。

我想知道在真正暂停线程之前jvm是否已经进行了一些旋转。 它预计,与你的一样,写得很好的关键部分几乎可以立即完成。 因此,在放弃和暂停线程之前,应该乐观地等待,我不知道,有几十个循环。 如果是这种情况,它应该与第二个版本相同。

分析器显示的内容可能与全速运行的jvm中发生的事情有很大的不同,具有各种疯狂的优化。 最好在没有分析器的情况下测量和比较吞吐量。

在进行这种同步优化之前,您确实需要一个分析器来告诉您这是绝对必要的。

是的,在某些条件下同步可能比primefaces操作慢,但比较原始方法和替换方法。 前者非常清晰,易于维护,后者非常复杂。 因此,可能存在非常微妙的并发错误,在初始测试期间您将找不到。 我已经看到一个问题, sizehead可能真的不同步,因为虽然这些操作中的每一个都是primefaces的,但组合不是,有时这可能会导致状态不一致。

所以,我的建议是:

  1. 从简单开始
  2. 轮廓
  3. 如果性能足够好,请保持简单的实现
  4. 如果你需要性能提升,那么就开始变得聪明(可能首先使用更专业的锁),以及TESTTESTTEST

这是忙等待锁的代码。

 public class BusyWaitLock { private static final boolean LOCK_VALUE = true; private static final boolean UNLOCK_VALUE = false; private final static Logger log = LoggerFactory.getLogger(BusyWaitLock.class); /** * @author Rod Moten * */ public class BusyWaitLockException extends RuntimeException { /** * */ private static final long serialVersionUID = 1L; /** * @param message */ public BusyWaitLockException(String message) { super(message); } } private AtomicBoolean lock = new AtomicBoolean(UNLOCK_VALUE); private final long maximumWaitTime ; /** * Create a busy wait lock with that uses the default wait time of two minutes. */ public BusyWaitLock() { this(1000 * 60 * 2); // default is two minutes) } /** * Create a busy wait lock with that uses the given value as the maximum wait time. * @param maximumWaitTime - a positive value that represents the maximum number of milliseconds that a thread will busy wait. */ public BusyWaitLock(long maximumWaitTime) { if (maximumWaitTime < 1) throw new IllegalArgumentException (" Max wait time of " + maximumWaitTime + " is too low. It must be at least 1 millisecond."); this.maximumWaitTime = maximumWaitTime; } /** * */ public void lock () { long startTime = System.currentTimeMillis(); long lastLogTime = startTime; int logMessageCount = 0; while (lock.compareAndSet(UNLOCK_VALUE, LOCK_VALUE)) { long waitTime = System.currentTimeMillis() - startTime; if (waitTime - lastLogTime > 5000) { log.debug("Waiting for lock. Log message # {}", logMessageCount++); lastLogTime = waitTime; } if (waitTime > maximumWaitTime) { log.warn("Wait time of {} exceed maximum wait time of {}", waitTime, maximumWaitTime); throw new BusyWaitLockException ("Exceeded maximum wait time of " + maximumWaitTime + " ms."); } } } public void unlock () { lock.set(UNLOCK_VALUE); } }