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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34928644/article/details/82809697

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);
			// 把值放入threadPool
			threadPool.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:[email protected]
 */
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>();
	//连接池默认大小为3
	private 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方法参数
											@Override
											public 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获取con
		Connection 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();
		}
		
	}
}

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_34928644/article/details/82809697