HikariCP 连接池–高性能数据结构

HikariCP,日语的含义是“光”,号称目前最快的数据库连接池。

它的高性能来自两个方面:

  1. 利用 Javassist 在字节码层面优化代理对象的创建,提升代理对象的调用性能。
  2. 在数据结构上采用定制的 FastListConcurrentBag 来提升性能。

本文主要关注这两种数据结构的实现。

1. FastList

我们用 JDBC 编程的时候,首先是获取 Connection、创建 Statement、执行查询得到 ResultSet,执行完成后依次关闭:ResultSetStatementConnection,特别是一个逻辑里创建了多个 PreparedStatement 时,一般用完就关闭。

为了防止用户忘了关闭 StatementResultSet,连接池需要跟踪创建的 StatementResultSet,在连接返回到连接池时关闭这两类资源。

public abstract class ProxyConnection implements Connection
{
    // 跟踪本连接创建的语句
    private final FastList<Statement> openStatements;

   private synchronized <T extends Statement> T trackStatement(final T statement) {
      openStatements.add(statement);
      return statement;
   }

   // Statement 关闭时回调此方法
   final synchronized void untrackStatement(final Statement statement) {
      openStatements.remove(statement);
   }

   // 创建语句时加入跟踪列表
   public Statement createStatement() throws SQLException {
      return ProxyFactory.getProxyStatement(this, trackStatement(delegate.createStatement()));
   }

    // Connection.close 方法回调此方法关闭打开的语句
    private synchronized void closeStatements() {
      final int size = openStatements.size();
      if (size > 0) {
         for (int i = 0; i < size && delegate != ClosedConnection.CLOSED_CONNECTION; i++) {
            // 利用 try-with-resources 机制进行关闭
            try (Statement ignored = openStatements.get(i)) {
               // automatic resource cleanup
            } catch (SQLException e) {
               LOGGER.warn("{} - Connection {} marked as broken because of an exception closing open statements during Connection.close()",
                           poolEntry.getPoolName(), delegate);
               leakTask.cancel();
               poolEntry.evict("(exception closing Statements during Connection.close())");
               // 包装的代理对象不再持有底层的连接
               delegate = ClosedConnection.CLOSED_CONNECTION;
            }
         }

         openStatements.clear();
      }
}

关闭的顺序跟创建的顺序是相反的,要关闭并移除的对象一般在列表的末尾。而 ArrayList 的移除对象是从列表头部开始的,在这种场景下不高效。FastList 的实现是逆序查找要删除对象;对于根据下标进行的操作,移除了对下标合法性的检查,由连接池来保证。

继续阅读

JUC 并发 Queue 设计与介绍

Queue 体系

Queue 是一种先进先出的队列。

ArrayBlockingQueue 和 LinkedBlockingQueue 是带阻塞特性,基于锁来实现。ArrayBlockingQueue 采用同一把锁来控制出、入队列操作;LinkedBlockingQueue 用两把锁来分别控制出、入队列操作,提高了并发性能。

ConcurrentLinkedQueue 非阻塞,采用无锁算法、利用 CAS 操作来实现。

0.1 BlockingQueue

当生产者向队列添加元素但队列已满时,生产者会被阻塞;当消费者从队列移除元素、但队列为空时,消费者会被阻塞。

其实现类必须是线程安全,入队列 happen-before 出队列。

0.2 TransferQueue

继承自 BlockingQueue,更进一步:生产者会一直阻塞直到添加到队列的元素被某一个消费者所消费(不仅仅是添加到队列)。

特别适用于这种应用间传递消息的场景:生产者有时需要等待消费者接收消息,有时只需把消息放进队列、不需要等待消费者接收。

// 传递元素给消费者,如果需要则等待。确保一次传递完成。
void transfer(E e);

// 非阻塞
boolean tryTransfer(E e);

// 基于等待时间的。
boolean tryTransfer(E e, long timeout, TimeUnit unit);

// 返回是否有在等待接收元素的消费者
// (BlockingQueue.take()或带等待时间的 poll 方法调用)
boolean hasWaitingConsumer();

// 返回大概的在等待接收元素的消费者
//(BlockingQueue.take()或带等待时间的 poll 方法调用)
int getWaitingConsumerCount();

0.3 Deque

允许在两端进行插入、删除元素的线性集合。

实现类:

  • ArrayDeque:基于数组加头尾两个指针来实现、非线程安全的。
  • LinkedList:基于双向链表实现、非线程安全的。
  • ConcurrentLinkedDeque:基于双向链表、CAS 元语实现、无界的。

0.4 BlockingDeque

当 Deque 里没有元素时阻塞消费者,当没有空闲空间时阻塞生产者。

目前只有一个实现类 LinkedBlockingDeque,使用双向链表来存储元素,支持容量限制,用一把锁来保证线程安全性。因为允许在两端进行操作,双向链表更合适。

继续阅读

Akka 文档:术语、概念

翻译自:http://doc.akka.io/docs/akka/2.3.8/general/terminology.html

术语、概念

在本章,我们尝试建立一个通用的术语来定义一个坚固的基础,用于沟通 Akka 致力于的关于并发、分布式系统。请注意,对于很多这些术语,没有单一的认同的定义。我们仅仅试图给出在这篇 Akka 文档范围内使用的可行定义。

并发 vs. 并行

并发和并行是相关的概念,但他们有细微的不同。并发意味着两个或多个任务在取得进展,即使它们可能不是同时执行。例如通过时间分片来实现,任务的不同部分顺序地执行,混杂着其他任务的部分。并行在另一方面是 在真正地同时执行时出现。

异步 vs. 同步

如果调用者在方法返回一个值或抛出异常前不能取得进展,则个方法调用被认为是同步的。在另一方面,一个异步调用允许调用者取得进展,在一个有限数量的步骤后,并且方法完成后会收到通知,通过额外的机制(可以是注册一个回调、一个 Future 或 一个消息)。

一个同步的 API 可能使用阻塞来实现同步,但不是必须的。一个 CPU 敏感的任务可能表现出类似阻塞的行为。一般情况下,倾向于使用异步的 API,因为它们保证系统可以取得进展。Actors 天生是异步的:一个 actor 在发出消息后可以继续取得进展,不需要等待实际的发送(delivery)发生。

非阻塞 vs. 阻塞

如果一个线程能够无限延迟其他一些线程,我们认为是阻塞的。一个好的例子是,资源可以被一个线程通过互斥独占地使用。如果某个线程无限地(例如意外地跑入一个死循环)持有资源,其他在等待资源的线程将不能取得进展。与此相反,非阻塞意味着没有线程能够无限地延迟其他线程。

非阻塞操作优于阻塞的,因为,当包含阻塞操作时系统的整体进展没法保证。

继续阅读

《Java 虚拟机并发编程》笔记

并发

线程数 = CPU可用核心数 / ( 1 – 阻塞系数 )
阻塞系数的取值在 0 - 1 之间,计算密集型任务的阻塞系数是 0,IO 密集型任务的阻塞系数接近于 1。

构建计算密集型并发应用程序的几点经验:
* 子任务的划分数不少于处理器核心数;
* 线程数多于处理器核心数对性能提升毫无帮助;
* 在子任务划分超过一定数量之后,再增加子任务划分数对于性能的提升将十分有限。

保持一个合理的划分数,并使所有处理器核心都有足够的工作量才是关键。

应该尽可能地提供共享不可变性,否则就应该遵循隔离可变性原则,即保证总是只有一个线程访问可变变量。

开多少个线程以及如何拆分问题都会影响到你的并发应用程序的性能,还要权衡每个子任务的工作负载和划分开销。

继续阅读