首页 > 编程语言 >Java多线程(4):ThreadLocal

Java多线程(4):ThreadLocal

时间:2022-10-27 06:55:05浏览次数:58  
标签:resource void Resource ThreadLocal new Java 多线程 public

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

 

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

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

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

 

 

 

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

 

 

 

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

 

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

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

 

 

 

还是以代码的形式来解读一下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、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操作(但很多码农不知道这一点)。

 

 


 

 

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

 

标签:resource,void,Resource,ThreadLocal,new,Java,多线程,public
From: https://www.cnblogs.com/xiangwang1111/p/16830014.html

相关文章

  • JavaScript works behind the scenes —— hoisting and TDZ(变量提升和暂时性死区)
    JavaScriptworksbehindthescenes——hoistingandTDZ(变量提升和暂时性死区)conceptMakessometypesofvariablesaccessible/usableinthecodebeforethey......
  • JavaScript works behind the scenes —— scope and scope chain(作用域和作用域链)
    JavaScriptworksbehindthescenes——scopeandscopechain(作用域和作用域链)whatisscope?(作用域的概念)Scope:Spaceandenvironmentinwhichacertain......
  • [java]StringTokenizei总结
    构造函数三种StringTokenizer(Stringstr)//构造一个用来解析str的StringTokenizer对象。java默认的分隔符是空格("")、制表符(\t)、换行符(\n)、回车符(\r)。Strin......
  • jdbc入门案例学习,java如何连接mysql,如何和mysql进行连接
    在学习了java基础和mysql以及SQL语法之后,那我们可以开始学习如何程序对数据库的数据进行操作,基本操作就是,查询,新增,更新,删除,四个基本操作,也是全部操作。这节我们将通过jdb......
  • Java基础__学习笔记__线程
    =-=b又让我想起了以前操作系统的线程 --进程是执行中的一段程序,而一个进程中执行中的每个任务即为一个线程--一个线程只可以属于一个进程,但一个进程能包含多个线程-......
  • 2、计算Java对象所占内存的大小
    当一个对象有多个属性,需要计算整个对象的大小时,可以借助org.apache.lucene工具类首先引入maven依赖<dependency><groupId>org.apache.lucene</groupId><artifac......
  • javaweb画图
    最近都在忙期中考试,写博客的频率有点低了,以下是一些基本代码,是王建民老师说的要建立自己的资料库,我直接建的资料库。画图html<!--定义表格宽度行距宽度<tr>行<td>......
  • javascript编程单线程之异步模式Asynchronous
    异步模式Asynchronous不会等待这个任务结束才开始执行下一个任务,开启之后立即执行下一个任务,后续逻辑一般会通过回调函数的方式定义,异步模式对js非常重要,没有异步任务单线......
  • JavaScript 节流和防抖
    前言本文主要记录了JavaScript节流和防抖,节流和防抖本质上是优化执行高频率代码的一种手段。例如:浏览器的mousemove、resize、scroll等事件在触发时,会不断地调用绑定的......
  • javascript编程单线程之异步模式Asynchronous
    异步模式Asynchronous不会等待这个任务结束才开始执行下一个任务,开启之后立即执行下一个任务,后续逻辑一般会通过回调函数的方式定义,异步模式对js非常重要,没有异步任务单......