当前位置: 代码迷 >> 综合 >> MySQL---当Java遇上MySQL⑨---ThreadLocal来说连接池
  详细解决方案

MySQL---当Java遇上MySQL⑨---ThreadLocal来说连接池

热度:83   发布时间:2023-10-21 16:35:04.0

ThreadLocal

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

用比较通俗的话讲:ThreadLocal的实例通常是做成单例的形式,是一个Map,key值对应的是Thread.currentThread()当前线程对象,value值对应的是当前线程所要共享的资源。这样就可以实现同一个线程执行不同的方法时,通过ThreadLocal实例获取到的共享资源是惟一的。

代码演示

把ThreadLocal实例当做项目中存放各个线程自己的共享资源的容器。

package cn.hncu.threadLocal;import java.util.Random;/** ThreadLocal 线程局部变量池* 原理:map容器,key:Thread.currentThread(),value:Object* 每次调用get时,get方法内部会自动获取当前线程,* 以当前线程作为key值去map中获取该key值所对应的value值。*/
public class ThreadLocalDemo {//创建一个线程局部变量池对象,通过采用java自带的和我们自己做的类,理解ThreadLocal原理//采用 java.lang.ThreadLocal类//private static ThreadLocal<Object> threadPool = new ThreadLocal<Object>();//采用 我们自己开发的 MyThreadLocal类private static MyThreadLocal<Object> threadPool = new MyThreadLocal<Object>();private static Random random = new Random( System.currentTimeMillis() );public static Object getValue() {System.out.println("当前线程:"+Thread.currentThread().getName());//从线程局部变量池中拿出当前线程中存储的值Object obj = threadPool.get();if ( obj == null ) {System.out.println(Thread.currentThread().getName()+"第一次访问ThreadLoacal,没有值...");obj = random.nextInt(100);// 把值放入threadPoolthreadPool.set(obj);}return obj;}
}

模仿ThreadLocal,自己实现一个MyThreadLocal。

package cn.hncu.threadLocal;import java.util.HashMap;
import java.util.Map;/** 自己实现一个 ThreadLocal,但是效率没有ThreadLocal高* 但是原理是一样的。*/
public class MyThreadLocal<T> {private Map<Thread, T> map = new HashMap<Thread, T>();public T get() {return map.get(Thread.currentThread());}public void set(T t) {map.put(Thread.currentThread(), t);}
}

测试类

package cn.hncu.threadLocal;/** 测试 ThreadLocal中同一个线程访问获取的值是否相同*/
public class TestThreadLocal {public static void main(String[] args) {//测试单线程:发现值是相同的。Object obj1 = ThreadLocalDemo.getValue();Object obj2 = ThreadLocalDemo.getValue();System.out.println( "obj1:"+obj1 );System.out.println( "obj2:"+obj2 );System.out.println("obj1==obj2:"+ (obj1 == obj2) );boolean boo = new Hello().isEqual(obj2);System.out.println( boo );System.out.println("--------下面是子线程-------");/* 测试加入多线程:发现即使有所线程当每个线程所对应的一个局部变量是不变的,* 不管通过什么方式获取,同一个线程中通过ThreadLocalDemo.getValue()* 获取到的值总是相同的,而不同线程之间获取到的值是不同的。*/new Thread() {public void run() {Object obj1 = ThreadLocalDemo.getValue();Object obj2 = ThreadLocalDemo.getValue();System.out.println( "obj1:"+obj1 );System.out.println( "obj2:"+obj2 );System.out.println("obj1==obj2:"+ (obj1 == obj2) );boolean boo = new Hello().isEqual(obj2);System.out.println( boo );};}.start();}
}

数据库连接池---一个线程只能有一个连接

在上个版本的数据库连接池中已经很好的实现了,但是在开发JavaEE项目时,通常是一个用户对应一个线程,如果采用上个版本的数据库连接池的话,在实现复杂事务时,可能多次去获取连接,而每次获取到的连接都是不同的连接,这样的话就无法实现复杂事务的回滚。为解决这一问题,改装上一个版本的连接池,加入ThreadLocal实现一个线程中最多只能存在一个数据库连接。

配置文件:myConnPool.properties

##MySQL
driver=com.mysql.jdbc.Driver
##url=jdbc:mysql://127.0.0.1:3306/hncu?useUnicode=true&characterEncoding=utf-8
##下面这一句等价于上面这一句 ,因为 第三个 '/'代表默认路径即'127.0.0.1:3306'
url=jdbc:mysql:///hncu?useUnicode=true&characterEncoding=utf-8
username=root
password=1234
size=4##Oracle
#driver=oracle.jdbc.driver.OracleDriver
#url=jdbc:oracle:thin://127.0.0.1:1521:orcl
#username=scott
#password=tiger
#size=4

MyConnPool类

package cn.hncu.threadLocal.jdbcConnPool;import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.Properties;
import java.util.Queue;/*** 我的数据库连接池:* 动态代理+ThreadLocal* 做成一个线程共享一个连接* CreateTime: 2018年9月21日 下午11:36:49	* @author 宋进宇  Email:447441478@qq.com*/
public class MyConnPool {//存放连接的队列private static Queue<Connection> conPool = new LinkedList<Connection>();/* 存放 Thread 和 con 的键值对,Thread为当前线程,隐示状态。* tlPool.get/set方法内部会通过Thread.currentThread()获取到当前线程。*/private static ThreadLocal<Connection> tlPool = new ThreadLocal<Connection>();//连接池默认大小为3private static int size = 3;static {Properties p = new Properties();try {//加载配置文件p.load( MyConnPool.class.getClassLoader().getResourceAsStream("myConnPool.properties"));//读取配置信息String driver = p.getProperty("driver");String url = p.getProperty("url");String username = p.getProperty("username");String password = p.getProperty("password");String strSize = p.getProperty("size");size = Integer.valueOf( strSize );//加载驱动Class.forName( driver );for( int i = 0; i < size; i++ ) {//因为匿名内部类需要调用该对象,所以用final修饰final Connection con = DriverManager.getConnection(url, username, password);//关键点//这是一个 Connection 接口的代理对象Object proxiedObj = Proxy.newProxyInstance(MyConnPool.class.getClassLoader(), new Class[] {Connection.class},new InvocationHandler() { //这个才是关键点//参数 proxy对象 就是proxiedObj对象, method就是被调用的方法对象 , args方法参数@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//判断被调用的方法是否是close()方法if( "close".equals( method.getName() ) ) {//移除当前线程所对应的连接对象。tlPool.set(null);//把连接对象返回连接池中conPool.add( (Connection) proxy );System.out.println("还回来一个conn...");return null;}return method.invoke(con, args);}});Connection con2 = (Connection) proxiedObj;conPool.add(con2);}} catch (IOException e) {throw new RuntimeException(e.getMessage(), e);} catch (ClassNotFoundException e) {throw new RuntimeException(e.getMessage(), e);} catch (SQLException e) {throw new RuntimeException(e.getMessage(), e);}}/*** 获取数据库连接对象* @return 数据库连接对象*/public synchronized static Connection getConnection() {//先通过tlPool获取conConnection con = tlPool.get();//判断con是否为空if( con == null ) {//能进来说明当前线程是没有获得到con的,从连接池中获取一个连接//判断连接池是否为空if( conPool.size() <= 0) {//能进来说明连接池为空,当前线程睡一下再重新调用getConnection()方法try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}return getConnection();}con = conPool.poll();/* 能到这里说明当前线程获取到了一个连接对象,* 把连接对象存储到tlPool中,这样就形成一个线程对应一个连接对象了。*/tlPool.set(con);}return con;}
}

测试类

package cn.hncu.threadLocal.jdbcConnPool;import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;public class TestMyConnPool {private static Connection con = null;public static void main(String[] args) {try {//con 在 main线程中获取到的con = MyConnPool.getConnection();Statement st = con.createStatement();st.executeQuery("show databases");new Thread(){public void run() {Connection con1 = null;try {//con1在子线程中获取到的con1 = MyConnPool.getConnection();Statement st = con1.createStatement();st.executeQuery("show databases");System.out.println(Thread.currentThread().getName()+":"+ (con1 == con) ); //false}catch (Exception e) {e.printStackTrace();}};}.start();//con2 也在main线程中获取到的Connection con2 = MyConnPool.getConnection();System.out.println(Thread.currentThread().getName()+":"+ (con2 == con) ); //true} catch (SQLException e) {e.printStackTrace();}}
}