druid 数据库连接池init

84 篇文章 23 订阅

一 序:

  因为一个偶发的线上数据库报警,使用的mybatis+druid

Caused by: java.sql.SQLException: No operations allowed after statement closed.

at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:964)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:897)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:886)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:860)
at com.mysql.jdbc.StatementImpl.checkClosed(StatementImpl.java:436)
at com.mysql.jdbc.PreparedStatement.setInternal(PreparedStatement.java:3334)
at com.mysql.jdbc.PreparedStatement.setLong(PreparedStatement.java:3362)
at com.alibaba.druid.pool.DruidPooledPreparedStatement.setLong(DruidPooledPreparedStatement.java:321)
at org.apache.ibatis.type.LongTypeHandler.setNonNullParameter(LongTypeHandler.java:31)
at org.apache.ibatis.type.LongTypeHandler.setNonNullParameter(LongTypeHandler.java:26)

at org.apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.java:53)

***************************************************

原因未知,参数也配置了,怀疑是使用的druid版本1.0.27有bug,导致链接超时了没有关闭释放。

等下个版本上线更新下druid版本验证看看。

看了下druid的源码,比较多,终点关心连接池部分。

二 DruidDataSource

这个类比较大,3000多行,关注下init

  final ReentrantLock lock = this.lock;
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new SQLException("interrupt", e);
        }

使用了锁,没有用同步:

下面的是参数设置及校验

            initCheck(); //判断数据库类型,这里关注是mysql
            initExceptionSorter(); //异常容器
            initValidConnectionChecker(); //根据driver初始化这里关注是MySqlValidConnectionChecker

            validationQueryCheck(); //校验配置validationquery

          dataSourceStat //统计相关参数设置

          connections = new DruidConnectionHolder[maxActive];  //新建连接池
            evictConnections = new DruidConnectionHolder[maxActive];
            keepAliveConnections = new DruidConnectionHolder[maxActive];

        if (!asyncInit) {
                try {
                    // init connections

                    for (int i = 0, size = getInitialSize(); i < size; ++i) {

                         //创建物理连接(包含了参数赋值,创建物理连接,初始化连接,连接有效性校验)

                        PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
                        DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
                        connections[poolingCount] = holder;//放入连接池
                        incrementPoolingCount();//计数增加
                    }
                    if (poolingCount > 0) {
                        poolingPeak = poolingCount;
                        poolingPeakTime = System.currentTimeMillis();
                    }
                } catch (SQLException ex) {
                    LOG.error("init datasource error, url: " + this.getUrl(), ex);
                    connectError = ex;
                }
            }
            createAndLogThread(); // 日志线程,一直运行,间隔timeBetweenLogStatsMillis
            createAndStartCreatorThread(); //创建连接的线程CreateConnectionThread,一直运行,连接池满了就等
     createAndStartDestroyThread();//创建销毁的线程DestroyConnectionThread,一直运行,核心方法removeAbandoned

            initedLatch.await();//主线程在计数器为0前一直等待。

            init = true;

 if (keepAlive) { //keepalive是1.0.29版本开始加入的,初始化连接池时会填充到minIdle数量,就是这里提现的。当然空闲检测也有相关代码
                // async fill to minIdle
                if (createScheduler != null) {
                    for (int i = 0; i < minIdle; ++i) {
                        createTaskCount++;
                        CreateConnectionTask task = new CreateConnectionTask();
                        this.createSchedulerFuture = createScheduler.submit(task);
                    }
                } else {
                    this.emptySignal();
                }
            }

************

相关知识点:

    private final CountDownLatch             initedLatch               = new CountDownLatch(2);

 就叫倒计时同步器。当前同步数为2,在变成0后,主线程才能运行,否则一直等待中。

        在创建连接与收缩池子的线程中都有initedLatch.countDown();

demo:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {

	static CountDownLatch c = new CountDownLatch(2);

	public static void main(String[] args) throws InterruptedException {
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("a");
				c.countDown();
				System.out.println("b");
				c.countDown();
			}
		}).start();

		c.await();
		System.out.println("c");
	}
}

输出:a

b

c

CountDownLatch底层的实现是sync.也是AQS的子类:

    private static final class Sync extends AbstractQueuedSynchronizer {

可以取看下对应的代码,AQS实现的很巧妙,依赖队列的头结点,挂起,唤醒等实现了共享锁传播。

共享锁是否可以被获取的判断为空方法,交由子类(sync)去实现。

当然在创建物理连接时:

    protected static AtomicIntegerFieldUpdater<DruidAbstractDataSource> createCountUpdater          = AtomicIntegerFieldUpdater.newUpdater(DruidAbstractDataSource.class, "creatingCount");

这里使用了atomic包的新子类,补充下相关类:

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下三个类:

AtomicReference:原子更新引用类型。

AtomicReferenceFieldUpdater:原子更新引用类型里的字段。

AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。

如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类

AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。

AtomicLongFieldUpdater:原子更新长整型字段的更新器。

AtomicStampedReference:原子更新带有版本号的引用类型

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值