为了账号安全,请及时绑定邮箱和手机立即绑定

Java多线程(4):ThreadLocal

标签:
Java

您好,我是湘王,这是我的慕课手记,欢迎您来,欢迎您再来~


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

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

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

https://img1.sycdn.imooc.com//63592ab70001872003560229.jpg

 

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

https://img1.sycdn.imooc.com//63592abf0001b9ff04710288.jpg


而这就是ThreadLocal最核心的思想

 

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

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

https://img1.sycdn.imooc.com//63592ac90001546704930282.jpg

 

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

/**
 * 资源类
 *
 * @author 湘王
 */
public class Resource {
   private String name;
   private String value;

   public Resource(String name, String value) {
      super();
      this.name = name;
      this.value = value;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public String getValue() {
      return value;
   }

   public void setValue(String value) {
      this.value = value;
   }
}

 

分别有ResuorceUtils1、ResuorceUtils2ResuorceUtils3分别以不同的方式来连接资源那么看看效率如何

/**
 * 连接资源工具类,通过静态方式获得连接
 *
 * @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实现了线程的资源复用

 

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

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

https://img1.sycdn.imooc.com//63592b0000015ec005700282.jpg

 

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

https://img1.sycdn.imooc.com//63592b06000191e005700232.jpg


 

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

https://img1.sycdn.imooc.com//63592b0b0001dc7505700232.jpg


 

理解了之后再来看一个数据传递的例子也就是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的数据都被Data2Data3拿到了就像这样

https://img1.sycdn.imooc.com//63592b1f0001117406210305.jpg


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

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

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




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



点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消