• Java多线程(4):ThreadLocal


    您好,我是湘王,这是我的CSDN博客,欢迎您来,欢迎您再来~

    为了提高CPU的利用率,工程师们创造了多线程。但是线程们说:要有光!(为了减少线程创建(T1启动)和销毁(T3切换)的时间),于是工程师们又接着创造了线程池ThreadPool。就这样就可以了吗?——不,工程师们并不满足于此,他们不把自己创造出来的线程给扒个底朝天决不罢手。

    有了线程关键字解决线程安全问题,有了线程池解决效率问题,那还有什么问题是可以需要被解决的呢?——还真被这帮疯子攻城狮给找到了!

    当多个线程共享同一个资源的时候,为了保证线程安全,有时不得不给资源加锁,例如使用Synchronized关键字实现同步锁。这本质上其实是一种时间换空间的搞法——用单一资源让不同的线程依次访问,从而实现内容安全可控。就像这样:

    但是,可以不可以反过来,将资源拷贝成多份副本的形式来同时访问,达到一种空间换时间的效果呢?当然可以,就像这样:

    而这,就是ThreadLocal最核心的思想。

    但这种方式在很多应用级开发的场景中用得真心不多,而且有些公司还禁止使用ThreadLocal,因为它搞不好还会带来一些负面影响。

    其实,从拷贝若干副本这种功能来看,ThreadLocal是实现了在线程内部存储数据的能力的,而且相互之间还能通信。就像这样:

    还是以代码的形式来解读一下ThreadLocal。有一个资源类Resource:

    1. /**
    2. * 资源类
    3. *
    4. * @author 湘王
    5. */
    6. public class Resource {
    7. private String name;
    8. private String value;
    9. public Resource(String name, String value) {
    10. super();
    11. this.name = name;
    12. this.value = value;
    13. }
    14. public String getName() {
    15. return name;
    16. }
    17. public void setName(String name) {
    18. this.name = name;
    19. }
    20. public String getValue() {
    21. return value;
    22. }
    23. public void setValue(String value) {
    24. this.value = value;
    25. }
    26. }

    分别有ResuorceUtils1、ResuorceUtils2和ResuorceUtils3分别以不同的方式来连接资源,那么看看效率如何。

    1. /**
    2. * 连接资源工具类,通过静态方式获得连接
    3. *
    4. * @author 湘王
    5. */
    6. public class ResourceUtils1 {
    7. // 定义一个静态连接资源
    8. private static Resource resource = null;
    9. // 获取连接资源
    10. public static Resource getResource() {
    11. if(resource == null) {
    12. resource = new Resource("xiangwang", "123456");
    13. }
    14. return resource;
    15. }
    16. // 关闭连接资源
    17. public static void closeResource() {
    18. if(resource != null) {
    19. resource = null;
    20. }
    21. }
    22. }
    23. /**
    24. * 连接资源工具类,通过实例化方式获得连接
    25. *
    26. * @author 湘王
    27. */
    28. public class ResourceUtils2 {
    29. // 定义一个连接资源
    30. private Resource resource = null;
    31. // 获取连接资源
    32. public Resource getResource() {
    33. if(resource == null) {
    34. resource = new Resource("xiangwang", "123456");
    35. }
    36. return resource;
    37. }
    38. // 关闭连接资源
    39. public void closeResource() {
    40. if(resource != null) {
    41. resource = null;
    42. }
    43. }
    44. }
    45. /**
    46. * 连接资源工具类,通过线程中的static Connection的副本方式获得连接
    47. *
    48. * @author 湘王
    49. */
    50. public class ResourceUtils3 {
    51. // 定义一个静态连接资源
    52. private static Resource resource = null;
    53. private static ThreadLocal resourceContainer = new ThreadLocal();
    54. // 获取连接资源
    55. public static Resource getResource() {
    56. synchronized(ResourceManager.class) {
    57. resource = resourceContainer.get();
    58. if(resource == null) {
    59. resource = new Resource("xiangwang", "123456");
    60. resourceContainer.set(resource);
    61. }
    62. return resource;
    63. }
    64. }
    65. // 关闭连接资源
    66. public static void closeResource() {
    67. if(resource != null) {
    68. resource = null;
    69. resourceContainer.remove();
    70. }
    71. }
    72. }
    73. /**
    74. * 连接资源管理类
    75. *
    76. * @author 湘王
    77. */
    78. public class ResourceManager {
    79. public void insert() {
    80. // 获取连接
    81. // System.out.println("Dao.insert()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
    82. // Resource resource = new ResourceUtils2().getResource();
    83. Resource resource = ResourceUtils3.getResource();
    84. System.out.println("Dao.insert()-->" + Thread.currentThread().getName() + resource);
    85. }
    86. public void delete() {
    87. // 获取连接
    88. // System.out.println("Dao.delete()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
    89. // Resource resource = new ResourceUtils2().getResource();
    90. Resource resource = ResourceUtils3.getResource();
    91. System.out.println("Dao.delete()-->" + Thread.currentThread().getName() + resource);
    92. }
    93. public void update() {
    94. // 获取连接
    95. // System.out.println("Dao.update()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
    96. // Resource resource = new ResourceUtils2().getResource();
    97. Resource resource = ResourceUtils3.getResource();
    98. System.out.println("Dao.update()-->" + Thread.currentThread().getName() + resource);
    99. }
    100. public void select() {
    101. // 获取连接
    102. // System.out.println("Dao.select()-->" + Thread.currentThread().getName() + ResourceUtils1.getResource());
    103. // Resource resource = new ResourceUtils2().getResource();
    104. Resource resource = ResourceUtils3.getResource();
    105. System.out.println("Dao.select()-->" + Thread.currentThread().getName() + resource);
    106. }
    107. public void close() {
    108. ResourceUtils3.closeResource();
    109. }
    110. public static void main(String[] args) {
    111. for (int i = 0; i < 3; i++) {
    112. new Thread(new Runnable() {
    113. ResourceManager rm = new ResourceManager();
    114. @Override
    115. public void run() {
    116. rm.insert();
    117. rm.delete();
    118. rm.update();
    119. rm.select();
    120. rm.close();
    121. }
    122. }).start();
    123. }
    124. }
    125. }

    执行ResourceManager类中的main()方法后,可以清楚地看到:

    第一种静态方式:大部分资源都能复用,但毫无规律;

    第二种实例方式:即使是同一个线程,资源实例也不一样;

    第三种ThreadLocal静态方式:相同的线程有相同的实例。

    结论是:ThreadLocal实现了线程的资源复用。

    也可以通过画图的方式来看清楚三者之间的不同:

    这是静态方式下的资源管理:

    这是实例方式下的资源管理:

    这是ThreadLocal静态方式下的资源管理:

    理解了之后,再来看一个数据传递的例子,也就是ThreadLocal实现线程间通信的例子:

    1. /**
    2. * 数据传递
    3. *
    4. * @author 湘王
    5. */
    6. public class DataDeliver {
    7. static class Data1 {
    8. public void process() {
    9. Resource resource = new Resource("xiangwang", "123456");
    10. //将对象存储到ThreadLocal
    11. ResourceContextHolder.holder.set(resource);
    12. new Data2().process();
    13. }
    14. }
    15. static class Data2 {
    16. public void process() {
    17. Resource resource = ResourceContextHolder.holder.get();
    18. System.out.println("Data2拿到数据: " + resource.getName());
    19. new Data3().process();
    20. }
    21. }
    22. static class Data3 {
    23. public void process() {
    24. Resource resource = ResourceContextHolder.holder.get();
    25. System.out.println("Data3拿到数据: " + resource.getName());
    26. }
    27. }
    28. static class ResourceContextHolder {
    29. public static ThreadLocal holder = new ThreadLocal<>();
    30. }
    31. public static void main(String[] args) {
    32. new Data1().process();
    33. }
    34. }

    运行代码之后,可以看到Data1的数据都被Data2和Data3拿到了,就像这样:

    ThreadLocal在实际应用级开发中较少使用,因为容易造成OOM:

    1、由于ThreadLocal是一个弱引用(WeakReference>),因此会很容易被GC回收;

    2、但ThreadLocalMap的生命周期和Thread相同,这就会造成当key=null时,value却还存在,造成内存泄漏。所以,使用完ThreadLocal后需要显式调用remove操作(但很多码农不知道这一点)。


    感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~

  • 相关阅读:
    基于springboot的个人博客管理系统
    《嵌入式虚拟化技术与应用》:深入浅出阐述嵌入式虚拟机原理,实现“小而能”嵌入式虚拟机!
    Windows详细安装和彻底删除RabbitMQ图文流程
    【Kali安全渗透测试实践教程】第7章 权限提升
    uniapp自定义底部导航
    【LeetCode-中等题】18. 四数之和
    MySQL 学习笔记(二)MVCC 机制
    LabVIEW开发锅炉汽包水位的监督控制和模拟
    如何制作一个体温收集表
    STM32-HAL库06-硬件IIC驱动FM24CL16B非易失存储器
  • 原文地址:https://blog.csdn.net/lostrex/article/details/127540085