您好 ,我是湘王 ,这是我的博客园 ,欢迎您来 ,欢迎您再来~
为了提高CPU的利用率 ,工程师们创造了多线程 。但是线程们说:要有光!(为了减少线程创建(T1启动)和销毁(T3切换)的时间) ,于是工程师们又接着创造了线程池ThreadPool 。就这样就可以了吗?——不 ,工程师们并不满足于此 ,他们不把自己创造出来的线程给扒个底朝天决不罢手 。
有了线程关键字解决线程安全问题 ,有了线程池解决效率问题 ,那还有什么问题是可以需要被解决的呢?——还真被这帮疯子攻城狮给找到了!
当多个线程共享同一个资源的时候 ,为了保证线程安全 ,有时不得不给资源加锁,例如使用Synchronized关键字实现同步锁 。这本质上其实是一种时间换空间的搞法——用单一资源让不同的线程依次访问 ,从而实现内容安全可控 。就像这样:
但是 ,可以不可以反过来,将资源拷贝成多份副本的形式来同时访问 ,达到一种空间换时间的效果呢?当然可以 ,就像这样:
而这 ,就是ThreadLocal最核心的思想 。
但这种方式在很多应用级开发的场景中用得真心不多 ,而且有些公司还禁止使用ThreadLocal ,因为它搞不好还会带来一些负面影响 。
其实 ,从拷贝若干副本这种功能来看 ,ThreadLocal是实现了在线程内部存储数据的能力的 ,而且相互之间还能通信 。就像这样:
还是以代码的形式来解读一下ThreadLocal 。有一个资源类Resource:
分别有ResuorceUtils1 、ResuorceUtils2和ResuorceUtils3分别以不同的方式来连接资源 ,那么看看效率如何 。
/**
* 连接资源工具类 ,通过静态方式获得连接
*
* @author 湘王
*/
public class ResourceUtils1 {
// 定义一个静态连接资源
private static Resource resource = null;
// 获取连接资源
public static Resource getResource() {
if(resource == null) {
resource = new Resource("xiangwang", "123456");
}
return resource;
}
// 关闭连接资源
public static void closeResource() {
if(resource != null) {
resource = null;
}
}
}
/**
* 连接资源工具类 ,通过实例化方式获得连接
*
* @author 湘王
*/
public class ResourceUtils2 {
// 定义一个连接资源
private Resource resource = null;
// 获取连接资源
public Resource getResource() {
if(resource == null) {
resource = new Resource("xiangwang", "123456");
}
return resource;
}
// 关闭连接资源
public void closeResource() {
if(resource != null) {
resource = null;
}
}
}
/**
* 连接资源工具类 ,通过线程中的static Connection的副本方式获得连接
*
* @author 湘王
*/
public class ResourceUtils3 {
// 定义一个静态连接资源
private static Resource resource = null;
private static ThreadLocal<Resource> resourceContainer = new ThreadLocal<Resource>();
// 获取连接资源
public static Resource getResource() {
synchronized(ResourceManager.class) {
resource = resourceContainer.get();
if(resource == null) {
resource = new Resource("xiangwang", "123456");
resourceContainer.set(resource);
}
return resource;
}
}
// 关闭连接资源
public static void closeResource() {
if(resource != null) {
resource = null;
resourceContainer.remove();
}
}
}
/**
* 连接资源管理类
*
* @author 湘王
*/
public class ResourceManager {
public void insert() {
// 获取连接
// System.out.println("Dao.insert()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
// Resource resource = new ResourceUtils2().getResource();
Resource resource = ResourceUtils3.getResource();
System.out.println("Dao.insert()-->" + Thread.currentThread().getName() + resource);
}
public void delete() {
// 获取连接
// System.out.println("Dao.delete()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
// Resource resource = new ResourceUtils2().getResource();
Resource resource = ResourceUtils3.getResource();
System.out.println("Dao.delete()-->" + Thread.currentThread().getName() + resource);
}
public void update() {
// 获取连接
// System.out.println("Dao.update()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
// Resource resource = new ResourceUtils2().getResource();
Resource resource = ResourceUtils3.getResource();
System.out.println("Dao.update()-->" + Thread.currentThread().getName() + resource);
}
public void select() {
// 获取连接
// System.out.println("Dao.select()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
// Resource resource = new ResourceUtils2().getResource();
Resource resource = ResourceUtils3.getResource();
System.out.println("Dao.select()-->" + Thread.currentThread().getName() + resource);
}
public void close() {
ResourceUtils3.closeResource();
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
ResourceManager rm = new ResourceManager();
@Override
public void run() {
rm.insert();
rm.delete();
rm.update();
rm.select();
rm.close();
}
}).start();
}
}
}
执行ResourceManager类中的main()方法后,可以清楚地看到:
第一种静态方式:大部分资源都能复用 ,但毫无规律;
第二种实例方式:即使是同一个线程 ,资源实例也不一样;
第三种ThreadLocal静态方式:相同的线程有相同的实例 。
结论是:ThreadLocal实现了线程的资源复用 。
也可以通过画图的方式来看清楚三者之间的不同:
这是静态方式下的资源管理:
这是实例方式下的资源管理:
这是ThreadLocal静态方式下的资源管理:
理解了之后,再来看一个数据传递的例子 ,也就是ThreadLocal实现线程间通信的例子:
/**
* 数据传递
*
* @author 湘王
*/
public class DataDeliver {
static class Data1 {
public void process() {
Resource resource = new Resource("xiangwang", "123456");
//将对象存储到ThreadLocal
ResourceContextHolder.holder.set(resource);
new Data2().process();
}
}
static class Data2 {
public void process() {
Resource resource = ResourceContextHolder.holder.get();
System.out.println("Data2拿到数据: " + resource.getName());
new Data3().process();
}
}
static class Data3 {
public void process() {
Resource resource = ResourceContextHolder.holder.get();
System.out.println("Data3拿到数据: " + resource.getName());
}
}
static class ResourceContextHolder {
public static ThreadLocal<Resource> holder = new ThreadLocal<>();
}
public static void main(String[] args) {
new Data1().process();
}
}
运行代码之后 ,可以看到Data1的数据都被Data2和Data3拿到了 ,就像这样:
ThreadLocal在实际应用级开发中较少使用 ,因为容易造成OOM:
1 、由于ThreadLocal是一个弱引用(WeakReference<ThreadLocal<?>>) ,因此会很容易被GC回收;
2 、但ThreadLocalMap的生命周期和Thread相同 ,这就会造成当key=null时 ,value却还存在 ,造成内存泄漏。所以 ,使用完ThreadLocal后需要显式调用remove操作(但很多码农不知道这一点) 。
感谢您的大驾光临!咨询技术 、产品 、运营和管理相关问题 ,请关注后留言 。欢迎骚扰 ,不胜荣幸~
声明:本站所有文章 ,如无特殊说明或标注,均为本站原创发布。任何个人或组织 ,在未征得本站同意时 ,禁止复制 、盗用 、采集 、发布本站内容到任何网站 、书籍等各类媒体平台 。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 。