版权声明:本文为博主原创文章,未经博主允许不得转载。 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();
}
}
}
扫描二维码关注公众号,回复:
3286317 查看本文章