java并发编程-----深入剖析ThreadLocal

一.对ThreadLocal的理解

 ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

  这句话从字面上看起来很容易理解,但是真正理解并不是那么容易。

  我们还是先来看一个例子:

class ConnectionManager {private static Connection connect = null;public static Connection openConnection() {if(connect == null){connect = DriverManager.getConnection();}return connect;}public static void closeConnection() {if(connect!=null)connect.close();}
}

  假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问题:第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。

  所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。

  这样将会大大影响程序执行效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待。

  那么大家来仔细分析一下这个问题,这地方到底需不需要将connect变量进行共享?事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。

  到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。比如下面这样:

class ConnectionManager {private  Connection connect = null;public Connection openConnection() {if(connect == null){connect = DriverManager.getConnection();}return connect;}public void closeConnection() {if(connect!=null)connect.close();}
}class Dao{public void insert() {ConnectionManager connectionManager = new ConnectionManager();Connection connection = connectionManager.openConnection();//使用connection进行操作connectionManager.closeConnection();}
}

  这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。

  那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。

  但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

二.深入解析ThreadLocal类

  在上面谈到了对ThreadLocal的一些理解,那我们下面来看一下具体ThreadLocal是如何实现的。

  先了解一下ThreadLocal类提供的几个方法:

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

  get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,下面会详细说明。

  首先我们来看一下ThreadLocal类是如何为每个线程创建一个变量的副本的。

  先看下get方法的实现:

  第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是  this,而不是当前线程t。

  如果获取成功,则返回value值。

  如果map为空,则调用setInitialValue方法返回value。

  我们上面的每一句来仔细分析:

  首先看一下getMap方法中做了什么:

  可能大家没有想到的是,在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。

  那么我们继续取Thread类中取看一下成员变量threadLocals是什么:

  实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:

  可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。

  然后再继续看setInitialValue方法的具体实现:

  很容易了解,就是如果map不为空,就设置键值对,为空,再创建Map,看一下createMap的实现:

  至此,可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的:

  首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

  初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

  然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

  下面通过一个例子来证明通过ThreadLocal能达到在每个线程中创建变量副本的效果:

public class Test {ThreadLocal<Long> longLocal = new ThreadLocal<Long>();ThreadLocal<String> stringLocal = new ThreadLocal<String>();public void set() {longLocal.set(Thread.currentThread().getId());stringLocal.set(Thread.currentThread().getName());}public long getLong() {return longLocal.get();}public String getString() {return stringLocal.get();}public static void main(String[] args) throws InterruptedException {final Test test = new Test();test.set();System.out.println(test.getLong());System.out.println(test.getString());Thread thread1 = new Thread(){public void run() {test.set();System.out.println(test.getLong());System.out.println(test.getString());};};thread1.start();thread1.join();System.out.println(test.getLong());System.out.println(test.getString());}
}

  

  从这段代码的输出结果可以看出,在main线程中和thread1线程中,longLocal保存的副本值和stringLocal保存的副本值都不一样。最后一次在main线程再次打印副本值是为了证明在main线程中和thread1线程中的副本值确实是不同的。

  总结一下:

  1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;

  2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;

  3)在进行get之前,必须先set,否则会报空指针异常;

      如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

    因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。

 

看下面这个例子:

public class Test {ThreadLocal<Long> longLocal = new ThreadLocal<Long>();ThreadLocal<String> stringLocal = new ThreadLocal<String>();public void set() {longLocal.set(Thread.currentThread().getId());stringLocal.set(Thread.currentThread().getName());}public long getLong() {return longLocal.get();}public String getString() {return stringLocal.get();}public static void main(String[] args) throws InterruptedException {final Test test = new Test();System.out.println(test.getLong());System.out.println(test.getString());Thread thread1 = new Thread(){public void run() {test.set();System.out.println(test.getLong());System.out.println(test.getString());};};thread1.start();thread1.join();System.out.println(test.getLong());System.out.println(test.getString());}
}

  在main线程中,没有先set,直接get的话,运行时会报空指针异常。

  但是如果改成下面这段代码,即重写了initialValue方法:

public class Test {ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){protected Long initialValue() {return Thread.currentThread().getId();};};ThreadLocal<String> stringLocal = new ThreadLocal<String>(){;protected String initialValue() {return Thread.currentThread().getName();};};public void set() {longLocal.set(Thread.currentThread().getId());stringLocal.set(Thread.currentThread().getName());}public long getLong() {return longLocal.get();}public String getString() {return stringLocal.get();}public static void main(String[] args) throws InterruptedException {final Test test = new Test();test.set();System.out.println(test.getLong());System.out.println(test.getString());Thread thread1 = new Thread(){public void run() {test.set();System.out.println(test.getLong());System.out.println(test.getString());};};thread1.start();thread1.join();System.out.println(test.getLong());System.out.println(test.getString());}
}

  就可以直接不用先set而直接调用get了。

三.ThreadLocal的应用场景

  最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。

  如:

private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {return DriverManager.getConnection(DB_URL);
}
};public static Connection getConnection() {
return connectionHolder.get();
}

  

转载于:https://www.cnblogs.com/youzhongmin/p/6940411.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/484116.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VMware下主机与虚拟机剪切板独立,无法直接复制粘贴

看了很多博客都说需要重新安装vmware tools&#xff0c;但我使用的是这种方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 关闭虚拟机点击编辑虚拟机设置->选项选择客户机隔离&#xff0c;勾选上启动复制粘贴即可&#xff08;如果之前就已经勾选上但失效的情…

教AI区分因果关系和相关性,将改变下一代 AI 的研发

来源&#xff1a;ScienceAI编辑&#xff1a;萝卜皮多年前&#xff0c;AI 领域曾发生过一件荒唐而有趣的事情。据说&#xff0c;研究人员开发了一种对医院数据进行训练的算法。这个算法发现&#xff0c;「患有哮喘的肺炎患者的治愈表现&#xff0c;比没有哮喘的肺炎患者更好」&a…

Java Socket实现客户端服务端之间的通信

Java Socket Java Socket编程用于在不同JRE上运行的应用程序之间的通信。Java Socket编程可以是面向连接的或无连接的。Socket和ServerSocket类用于面向连接的套接字编程&#xff0c;DatagramSocket和DatagramPacket类用于无连接套接字编程。 此Demo将进行单向客户端和服务器…

数据结构-线性结构

一.线性结构 1.顺序线性表 1.1 线性结构是一种基本的数据结构&#xff0c;具有单一前驱和后继的数据关系描述。 1.2 线性表的存储结构分为顺序存储和链式存储。 1.3 顺序线性表的元素间的逻辑关系无需占用额外的空间来存储。 1.4 一般地&#xff0c;以LOC(a1a_1a1​)表示线性…

脑智前沿科普|虚拟现实如何欺骗你的大脑

来源&#xff1a;公众号&#xff08;脑与心智毕生发展研究中心CLIMB&#xff09;编辑&#xff1a;Yezi审阅&#xff1a;mingzlee7虚拟现实(Virtual reality, VR)就像被神奇地传送到另一个世界。这是一项令人兴奋的技术&#xff0c;但当我们戴上耳机后&#xff0c;我们很少停下来…

Linux下Java连接数据库出现 Access denied for user 'root'@'localhost' (using password: YES)错误

一、问题描述 centos 7 终端键入 mysql -u root -p 输入密码后可成功访问数据库&#xff0c;但使用IDEA编写Java代码实现数据库连接时却出现Access denied for user ‘root’‘localhost’ (using password: YES) 错误。 二、解决方法 在刚安装mysql时&#xff0c;系统会默认…

被骗两次?黄仁勋骗过世界的14秒,英伟达押宝未来的元宇宙……

来源&#xff1a;物联网智库在近期召开的计算机图形顶级会议ACM SIGGRAPH 2021 上&#xff0c;英伟达介绍了自研的3D仿真模拟和协作平台Omniverse&#xff0c;并放出了“合成版老黄”的打造过程。有媒体据此报道称——万万没想到&#xff0c;在3个月前的GTC大会中&#xff0c;厨…

QT清单打印程序

1.主要代码 //customer.h文件 #ifndef CUSTOMER_H #define CUSTOMER_H#include <QObject> #include <string>class Customer : public QObject {Q_OBJECT public:explicit Customer(QObject *parent nullptr);Customer(const Customer& customer);void setCo…

图灵奖得主杨立昆:人工智能比你更聪明吗?

来源&#xff1a;混沌巡洋舰人工智能常常被认为是一项将要颠覆世界的技术&#xff0c;从这一概念诞生至今的65年中&#xff0c;无数电影与小说塑造了各种经典的人工智能角色&#xff0c;AI &#xff08;Artificial Intelligence&#xff09;也很快成为人类未来世界蓝图中的重要…

(转)Windows系统、Linux系统 和 Mac OS操作系统 历史由来 与 区别?

目录 1 UNIX 由来 2 Linux 由来 3 Windows与Linux的主要区别 4 关于Linux的一些疑惑 参考资料 目前常见的三大操作系统&#xff1a;Windows系统、Linux系统 和 Mac OS操作系统。 首先&#xff0c;不管是Windows操作系统、Linux系统还是苹果的Mac OS操作系统&#xff0c;甚…

国务院公布《关键信息基础设施安全保护条例》

来源&#xff1a;中国政府网编辑&#xff1a;蒲蒲据中国政府网8月17日消息&#xff0c;《关键信息基础设施安全保护条例》已经2021年4月27日国务院第133次常务会议通过&#xff0c;现予公布&#xff0c;自2021年9月1日起施行。条例指出&#xff0c;国家对关键信息基础设施实行重…

文章推荐 | 城市规划中城市信息学的研究进展

来源&#xff1a;北京城市实验室BCL随着计算机技术的飞速发展&#xff0c;城市信息学作为城市规划领域的一门新兴学科&#xff0c;逐渐引起学术界的关注。城市信息学的兴起给城市规划带来了新的压力&#xff0c;但它也提供了新的城市分析视角。在此背景下&#xff0c;专家小组概…

Matlab基础

一.入门基础 1.基本知识 1.1 输入命令 以分号结尾不会打印变量的值 x 1 y x;1.2 变量命名规则&#xff1a;以字母开头&#xff0c;并且仅包含字母、数字和下划线。 1.3 使用save命令将工作区中的变量保存到MAT文件的MATLAB特定格式文件中,文件名为datafile.mat。可以指定保…

3年规模翻7倍统治 5G、IoT时代,化合物半导体材料深度报告

来源 华西证券编辑&#xff1a;智东西内参作者&#xff1a;吴吉森 等随着 5G、IoT 物联网时代的来临&#xff0c;以砷化镓&#xff08;GaAs&#xff09;、氮化镓&#xff08;GaN&#xff09;、碳化硅&#xff08;SiC&#xff09;为代表的化合物半导体市场有望快速崛起。其中&am…

SpringBoot笔记整理(二)

SpringBoot笔记整理&#xff08;一&#xff09; SpringBoot笔记整理&#xff08;二&#xff09; SpringBoot笔记整理&#xff08;三&#xff09; SpringBoot笔记整理&#xff08;四&#xff09; Spring Boot与日志&#xff08;日志框架、日志配置&#xff09; 1、市面上的日志…

MATLAB图像处理基础

1.导入数据 1.1 使用readtable("")导入数据&#xff0c;并存储在表格中&#xff0c;使用axis equal可以校正坐标轴纵横比。 letter readtable("M.txt"); plot(letter.X,letter.Y) axis equal1.2 range(x)函数返回x的值的范围&#xff0c;即max(x) - min(…

BBWebImage 设计思路

BBWebImage 设计思路 BBWebImage 是高性能 Swift 图片组件&#xff0c;用于图片下载、缓存、编解码、编辑与展示。 GitHub 地址&#xff1a; https://github.com/Silence-GitHub/BBWebImage 效果图 下载、展示并缓存原图 下载、渐进式解码、编辑图片&#xff0c;缓存编辑后的图…

清华本科、港科大准博士被指论文抄袭,网友:这是有技巧的“洗稿”

来源&#xff1a;整理自新智元、Reddit、知乎等不是吧&#xff1f;清华自动化本科&#xff0c;香港科技大学硕士生发表的顶会论文竟然是抄的&#xff1f;而且抄袭对象还是另一篇顶会论文&#xff1f;近日&#xff0c;眼尖的网友发现两篇分别发表在 ICML 2021 和 ICCV 2021 两大…

MATLAB深度学习入门

1. 加载图像 1.1 使用imread函数加载图像&#xff0c;可以加载GIF、JPEG、PNG等大多数标准文件格式图像。 Import an image img imread("file.jpg")1.2 采用**imshow()**来显示图像。 imshow(img)1.3 采用alexnet函数可以创建预定义的深度网络AlexNet的副本。 de…

SpringBoot笔记整理(三)

SpringBoot笔记整理&#xff08;一&#xff09; SpringBoot笔记整理&#xff08;二&#xff09; SpringBoot笔记整理&#xff08;三&#xff09; SpringBoot笔记整理&#xff08;四&#xff09; Web开发 1、使用SpringBoot&#xff1a; 1&#xff09;创建SpringBoot应用&…