HelloCoder HelloCoder
首页
《Java小白求职之路》
《小白学Java》
计算机毕设
  • 一些免费计算机资源
  • 脚手架工具
  • 《从0到1学习Java多线程》
  • 《从0到1搭建服务器》
随笔
关于作者
首页
《Java小白求职之路》
《小白学Java》
计算机毕设
  • 一些免费计算机资源
  • 脚手架工具
  • 《从0到1学习Java多线程》
  • 《从0到1搭建服务器》
随笔
关于作者
  • 《LearnJavaToFindAJob》

    • 导读

    • 【初级】6~12k档

    • 【中级】12k-26k档

      • JVM进阶

      • Java进阶

        • ConcurrentHashMap面试题
        • CopyOnWriteArrayList的实现原理
        • HashMap的put过程是怎么样的?
        • IO模型有哪些?
        • ThreadLocal的原理
        • valueOf、(String)强转有什么区别?
        • 为什么在lambda中使用的局部变量必须是final或有效final
        • 你知道如何更新缓存吗?如何保证缓存和数据库双写一致性?
        • 八股文之ReentrantLock
        • 分布式事务
        • 如何保障生产端消息投递成功?
        • 如何手动触发全量回收垃圾,如何立即触发垃圾回收
        • 数据库连接池为什么要使用ThreadLocal?
        • 线程池中多余的线程是如何回收的
        • 谈谈你对AQS的理解
        • 谈谈你对CAS的理解
        • util.concurrent的理解
        • 高并发下如何保证接口的幂等性?
      • MySQL

      • 中间件

      • 算法

      • 高阶

    • 【高级】26k+档

    • 大厂面试题

    • 求职建议

    • 面经

  • LearnJavaToFindAJob
  • 【中级】12k-26k档
  • Java进阶
#ThreadLocal
码农阿雨
2022-06-02
目录

数据库连接池为什么要使用ThreadLocal?

ThreadLocal 不知道大家日常有没有用到,反正HaC我是没有用过,但是这个东西确实是挺重要的,我之前在多线程的专题里面写过一篇。

常见的就是现在的C3P0、Druid线程池也会用到。

最近面试竟然被问到了ThreadLocal在线程池里面的作用,心态崩了呀~

复盘一下,如下。

# 1、为什么要引入数据库连接池?

数据库连接池大家可能不知道,但是线程池大家一定听过,其实数据库的连接池就是一个线程池,原生的JDBC,我们一般是这有操作数据库的:

Class.forName("com.mysql.jdbc.Driver");
java.sql.Connection conn = DriverManager.getConnection("jdbcUrl");

需要用到了就获取连接,不需要了就close()

C3P0、Druid 这种数据库工具都引入了线程池,把所有的数据库操作都交给线程池操作,这样的好处有:

1、 资源重用,假如一个请求频繁的操作数据库,每次都new 一个连接是十分消耗资源的。

2、更快的响应,建立连接是需要时间的,对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。

3、统一管理, 可根据预先的连接占用超时设定,防止强制收回被占用连接,避免在操作期间出错。

数据库连接池就是典型的用空间换时间的思想

虽然创建了多个连接对象丢在池里,会占用一定的内存空间,但是可以省去接下来每一次的创建连接、关闭连接时间。

在数据库的连接上,也是一个TCP的三次握手,其中还需要验证账户、密码、权限什么的,可想而知多耗时。

好比你每次写代码都要开机、打开IDEA;如果不关电脑下次直接就可以用IDEA写代码了,虽然费点“电脑”,但是效率却高了。

# 2、数据库的连接池是如何管理的?

上面为什么要引入数据库连接池这个问题还是很容易回答的,但是线程池是如何管理的这个问题就破防,大意了,我并不是很了解,现在来总结一下。

我以Durid为例(其实不止durid),线程池是通过 ThreadLocal 来管理的。

先来说一下ThreadLocal的原理:

1、每个Thread维护着一个ThreadLocalMap的引用

2、ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储

3、调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象

4、调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象

5、ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。

其为变量在每个线程中都创建了一个副本,每个线程都访问和修改本线程中变量的副本,但每个线程之间的变量是不能相互访问的,只能访问自己的。

如果你不用,那么可以维护一个全局变量(消耗资源),或者进行参数传递(得写多少无用代码,万一改了名字)

使用ThreadLocal的好处有:

1、代码调用DruidDataSource来管理conn连接,同时声明了ThreadLocal对象来保存每次线程请求所获取的连接,这样可以避免每个new一个JDBCUtils对象,将conn对象放在ThreadLocal对象中缓存起来,下次调用直接从ThreadLocal中获取来实现性能的提高。

2、保证事务的一致性。

每个线程保存的是该对象的一个副本,不同线程之间不会互相影响,假如一个DAO,你每次执行SQL操作的时候,是不是都要获取connection连接呢?如果有多个DAO操作,我们可以传参一个connection,但是这样写太麻烦了,而使用ThreadLocal,则在datasource.getConnection();的时候,直接就可以获取了,而且它可以保证当前线程中任何地方的Connection数据库连接都是相同的,也保证了事务的一致性。

来个例子:

//一个方法有两个dao操作
//方法一
DruidPooledConnection connection = datasource.getConnection();
PreparedStatement ps = connection.prepareStatement("select * from  user");
ResultSet resultSet = ps.executeQuery();
            
//方法二
DruidPooledConnection connection = datasource.getConnection();
PreparedStatement ps = connection.prepareStatement("update user set name = 'HaC' where id = 1");
Integer result = ps.executeUpdate();

这样的话Druid的getConnection()方法是从ThreadLocal获取的。

我们使用一些ORM框架,比如Mybatis、JPA等等无法感知这个过程,其实这些框架的底层也是如此。

当然Druid的功能不只是引入了ThreadLocal,还有线程池的大小控制、拒绝、超时管理等等很强大的功能。

可以尝试手写一个ThreadLocal来管理连接池:

public class C3P0Utils {
 
	private static DataSource source;//数据源
	static{
		source = new ComboPooledDataSource("mysql");//根据配置文件初始化数据源
	}
	
	/**
	 * 获取数据库连接
	 * @return
	 * @throws SQLException
	 */
	public static Connection getConnection() throws SQLException{
		return source.getConnection();
	}
}
public class TransactionThreadLocal {
 
	private static ThreadLocal<Connection> tc = new ThreadLocal<>();
	/**
	 * 返回当前线程的数据库连接
	 * @return
	 * @throws SQLException
	 */
	public static Connection getConnection() throws SQLException{
		Connection connection = tc.get();
		if(connection == null){
			connection = C3P0Utils.getConnection();
			tc.set(connection);
		}
		return connection;
	}
	
	/**
	 * 开启事务
	 * @throws SQLException
	 */
	public static void startTransaction() throws SQLException{
		getConnection().setAutoCommit(false);
	}
	/**
	 * 提交事务
	 * @throws SQLException
	 */
	public static void commit() throws SQLException{
		getConnection().commit();
	}
	
	/**
	 * 事务回滚
	 * @throws SQLException
	 */
	public static void rollback() throws SQLException{
		getConnection().rollback();
	}
	
	/**
	 * 关闭数据库
	 * @throws SQLException
	 */
	public static void close() throws SQLException{
		getConnection().close();//关闭数据库
		tc.remove();//将该线程的connection对象移除
	}
}
public class BookDaoImp implements BookDao{
    @Override
	public void edit(Book book) throws Exception {
		Connection conn = TransactionThreadLocal.getConnection();
		String sql = "update book set name=? where id=?";
		PreparedStatement pst = conn.prepareStatement(sql);
		pst.setString(1, "name");
		pst.setString(2, id);
		pst.execute();
	}
}

当然这个线程池的只放了一个连接,而Druid是可以自定义放很多个的,但是这个例子使用了ThreadLocal,可以避免每次都new 一个Connection,也不需要每次都把Connection作为参数传递。

阅读全文
×

(为防止恶意爬虫)
扫码或搜索:HelloCoder
发送:290992
即可永久解锁本站全部文章

解锁
#ThreadLocal
上次更新: 2025-02-21 06:04:57
最近更新
01
《LeetCode 101》
02-21
02
IDEA、Golang、Pycharm破解安装
02-21
03
《LeetCode CookBook》
02-21
更多文章>
Theme by Vdoing | Copyright © 2020-2025 码农阿雨
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式