浅谈ThreadLocal类
浅谈ThreadLocal类
1- ThreadLocal类
ThreadLocal是在java.lang包下的类,可见这是一个非常重要的类!这个类有什么作用呢?什么情况下需要使用这个类呢?首先看看jdk中的关于这个类的注释:以下截取了部分ThreadLocal类的注释:
这个类(ThreadLocal)提供了线程局部变量。线程局部变量与普通变量的不同之处在于线程局部变量是每一个线程都维护了一份副本的,相互独立并且可以通过get/set方法访问!通常ThreadLocal在使用时被private static修饰,以便将ThreadLocal中值与线程关联/绑定起来!
每一个线程在其生命周期内只要定义了线程局部变量并可以通过get/set方法访问,就都持有一份线程局部变量副本的引用;等这个线程持有的局部变量就将被垃圾收集器回收,除非这些线程局部变量还可以通过其他途径引用到!
由上可知,ThreadLocal可以为使用相同变量的每个不同的线程都创建不同的存储,这样就可以多线程在共享资源的使用上产生冲突!
2- ThreadLocal常用方法
ThreadLocal常用的方法,比如initialValue,get,set!
2.1-initialValue方法:
返回当前线程的线程局部变量的初始值,此方法在第一次使用get方法获取线程局部变量的时候被调用,如果在调用get方法之前使用了set方法设置了线程局部变量的值了,那么initialValue方法将不会被调用!通常此方法对于每个线程来说只会调用一次,除非在调用remove方法之后又调用了get方法!此处定义返回null,需要程序员重写这个方法定义在实际中初始的返回值!
2.2-get方法:
返回当前线程中持有的线程局部变量存储的值,如果没有这个值的话会先调用initialValue方法初始化一个线程局部变量并返回!
2.3-set方法
设置当前线程的线程局部变量的值,大多数情况下使用者不需要重写这个方法,方法的入参value将被存储在当前的线程的局部变量的副本中!
3- 示例代码
下面通过一个例子来说明ThreadLocal方法的使用!本例中模拟JDBC中,通过将连接保存到ThreadLocal对象中,这样每个线程都会拥有属于自己的连接,代码如下:
定义一个MyConnection类,表示连接:
package jdk.test.thread.local;
/**
* 连接
*/
public class MyConnection {
private String name;
public MyConnection() {
}
public MyConnection(String name) {
this.name = name;
}
/**
* 模拟执行SQL
*/
public void executeSQL(String sql) {
System.out.println("Connection:【" + name + "】,executeSQL:【" + sql + "】");
}
}
定义持有ThreadLocal的类:
package jdk.test.thread.local;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
/**
* ThreadLocal测试
*/
public class ThreadLocalVarHolder implements Runnable {
//假设这是个数据库连接池
private static List<MyConnection> connectionList = new ArrayList<>();
//假设一共有5个数据库连接
static {
connectionList = Lists.newArrayList(
new MyConnection("Con1"),
new MyConnection("Con2"),
new MyConnection("Con3"),
new MyConnection("Con4"),
new MyConnection("Con5"),
new MyConnection("Con6")
);
}
/**
* ThreadLocal:将线程中的某个值与保存这个值的对象关联起来
*/
private static ThreadLocal<MyConnection> value = new ThreadLocal<MyConnection>() {
public MyConnection initialValue() {
//默认每次随机取一个连接
if (connectionList.size() >= 1) {
return connectionList.remove((int) ((connectionList.size() - 1) * Math.random()));
} else {
throw new RuntimeException("没有可用的连接了!");
}
}
};
/**
* 获取连接的方法
*/
public static MyConnection getConnection() {
return value.get();
}
@Override
public void run() {
getConnection().executeSQL("select * from tableA;");
getConnection().executeSQL("update tableA set qty = 1 where id = 100;");
System.out.println("===================================");
connectionList.add(getConnection());
}
}
在本例中,模拟让每个持有连接的线程执行两条SQL,由于每个线程持有的线程局部变量MyConnection变量在此类初始化的时候就绑定了,run方法中三次调用getConnection方法返回的都是同一个MyConnection对象!
测试代码如下:
package jdk.test.thread.local;
import org.junit.Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* ThreadLocal测试类
*/
public class ThreadLocalTest {
/**
* 测试ThreadLocal的类,通过线程池工具提交的方法执行
* @throws Exception
*/
@Test
public void test() throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
executorService.execute(new ThreadLocalVarHolder());
}
TimeUnit.SECONDS.sleep(3);
executorService.shutdown();
}
}
运行测试方法,打印结果如下:
Connection:【Con5】,executeSQL:【select * from tableA;】
Connection:【Con5】,executeSQL:【update tableA set qty = 1 where id = 100;】
===================================
Connection:【Con6】,executeSQL:【select * from tableA;】
Connection:【Con6】,executeSQL:【update tableA set qty = 1 where id = 100;】
===================================
Connection:【Con4】,executeSQL:【select * from tableA;】
Connection:【Con4】,executeSQL:【update tableA set qty = 1 where id = 100;】
===================================
Connection:【Con6】,executeSQL:【select * from tableA;】
Connection:【Con6】,executeSQL:【update tableA set qty = 1 where id = 100;】
===================================
Connection:【Con4】,executeSQL:【select * from tableA;】
Connection:【Con4】,executeSQL:【update tableA set qty = 1 where id = 100;】
===================================
每个Connection出现两次!
上一篇: Zookeeper知识点
下一篇: zookeeper知识点总结