ThreadLocal的使用

    从名称看,ThreadLocal 也就是thread和local的组合,也就是一个thread有一个local的变量副本,ThreadLocal提供了线程的本地副本,也就是说每个线程将会拥有一个自己独立的变量副本。

    它与syncronized相反的思想,ThreadLocal 则从另一个角度来解决多线程的并发访问。 ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。 ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal 。  

     由于 ThreadLocal 中可以持有任何类型的对象,低版本 JDK 所提供的 get() 返回的是 Object 对象,需要强制类型转换。但 JDK 5.0 通过泛型很好的解决了这个问题,在一定程度地简化 ThreadLocal 的使用。 括起来说,对于多线程资源共享的问题,同步机制采用了 “ 以时间换空间 ” 的方式,而 ThreadLocal 采用了 “ 以空间换时间 ” 的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。 

package 线程池;

import java.util.Random;
import java.util.stream.IntStream;

/**
 * @author Heian
 * @time 19/03/18 11:51
 * 用途:
 */
public class ThreadLocalTest {

    private static ThreadLocal<Integer> local = new ThreadLocal<Integer>();

    public static void main(String[] args) {
        IntStream.range (0,3).forEach (value -> {
            new Thread (() -> {
                int num = new Random ().nextInt ();
                local.set (num);
                new A ().get (); new B ().get ();
            },"线程" + String.valueOf (value)).start ();
        });
    }

    static class A {
        public void get() {
            int data = local.get();
            System.out.println("A from " + Thread.currentThread().getName()
                    + " get data :" + data);

        }
    }

    static class B {
        public void get() {
            int data = local.get();
            System.out.println("B from " + Thread.currentThread().getName()
                    + " get data :" + data);

        }
    }
}

运行结果:

A from 线程1 get data :-263818661
A from 线程2 get data :1880424954
A from 线程0 get data :-766329914
B from 线程0 get data :-766329914
B from 线程2 get data :1880424954
B from 线程1 get data :-263818661

定义了一个全局变量local,在多线程的并发修改值的前提下,容易发生数据错乱,但是把其封装到Threadlocal中,可以发现,每个存值和取值都是互不影响的,hreadLocal的设计理念就是共享变私有,都已经私有了,还谈啥共享,因为看起来就如同没有共享变量了,不共享即安全,但是他并不是为了解决线程安全问题而存在的。

实际应用:每次请求spring会创建一个线程,通过请求获取到用户的信息,存入ThreadLocal类中,在去执行其它操作每个线程访问的对象由于存在各自的局部变量中,所以互不影响。

private final static ThreadLocal<SystemUser> userThreadLocal = new ThreadLocal<SystemUser>();
    
    @Autowired
    private SystemLogService systemLogService;

    /**
     * 设置当前用户
     * @author cary
     * @param user
     */
    public void setCurrentSystemUser(SystemUser user) {
        userThreadLocal.set(user);
    }
    /**
     * 从本地线程中取当前用户
     * @author cary
     * @return 系统用户
     */
    public SystemUser getCurrentSystemUser() {
        SystemUser user = userThreadLocal.get();
        if (null == user) {
            user = (SystemUser) SecurityUtils.getSubject().getPrincipal();
        }

        return user;
    }

ThreadLocal最根本的使用场景应该是:在每个线程希望有一个独有的变量时(这个变量还很可能需要在同一个线程内共享)

ThreadLocal 解决SimpleDateformat 线程不安全问题

线程安全问题展示:

public class test {

    private static ExecutorService executorService = Executors.newFixedThreadPool(10);

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");


    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            int num = i;
            executorService.execute(() -> {
                String s = DateFormatStr(num);
                System.out.println(s);
            });
        }
        executorService.shutdown();

    }

    public static String DateFormatStr(int second){
        String format = sdf.format(1000*second);
        return format;
    }

}
截取部分打印信息
1970-01-01 08:00:05
1970-01-01 08:00:05
1970-01-01 08:00:06
1970-01-01 08:00:05
1970-01-01 08:00:05
.......

为了方便我们观察我们用future改造下

public class Task {

    private static ExecutorService executorService = Executors.newFixedThreadPool(10);

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");


    public static void main(String[] args) {
        List<Future<String>> list = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            int num = i;
            Future<String> future = (Future<String>) executorService.submit(() -> {
                String s = DateFormatStr(num);
            });
            list.add(future);
        }
        List<String> result = new ArrayList<>();
        list.forEach(task -> {
            while (true){
                if (task.isDone()){
                    try {
                        result.add(task.get());
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    }finally {
                        break;
                    }

                }
            }
        });
        executorService.shutdown();
        List<String> collect = result.stream().sorted().collect(Collectors.toList());//报空指针
        System.out.println(collect);
    }

    public static String DateFormatStr(int second){
        String format = sdf.format(1000*second);
        return format;
    }


}

因为在线程池执行异步执行,result.stream() 每个element为null,sorted()方法要求每个element不为null,所以报错。需要稍微调整下

public class Task {

    private static ExecutorService executorService = Executors.newFixedThreadPool(10);

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");


    public static void main(String[] args) throws InterruptedException {
        List<Callable<String>> listCall = new ArrayList<>();
        //组装任务
        for (int i = 0; i < 50; i++) {
            int num = i;
            listCall.add(() -> {
                return DateFormatStr(num);
            });
        }
        List<Future<String>> futures = executorService.invokeAll(listCall);
        List<String> result = new ArrayList<>();
        futures.forEach(task -> {
            while (true){
                if (task.isDone()){
                    try {
                        result.add(task.get());
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    }finally {
                        break;
                    }

                }
            }
        });
        executorService.shutdown();
        List<String> collect = result.stream().sorted().collect(Collectors.toList());
        for (String s : collect) {
            System.out.println(s);
        }    
    }

    public static String DateFormatStr(int second){
        String format = sdf.format(1000*second);
        return format;
    }


}
1970-01-01 08:00:01, 
1970-01-01 08:00:04,
1970-01-01 08:00:04, 
.......

显然此集合是存在重复数据的,当然此处是可以用锁的方式去解决线程安全问题,只需要在DateFormatStr加上syncronized关键字即可

public static String DateFormatStr(int second){
    synchronized (sdf.getClass()){
        String format = sdf.format(1000*second);
        return format;
    }
}
1970-01-01 08:00:00
1970-01-01 08:00:01
1970-01-01 08:00:02
.....
1970-01-01 08:00:48
1970-01-01 08:00:49

那通过ThreadLocal怎么解决呢?也很简单,只要在每个线程中存储属于自己的一份变量即可,说白了就是以空间交换了时间

public class Task3 {

    private static ExecutorService executorService = Executors.newFixedThreadPool(10);

    private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        List<Callable<String>> listCall = new ArrayList<>();
        //组装任务
        for (int i = 0; i < 50; i++) {
            int num = i;
            listCall.add(() -> {
                return DateFormatStr(num);
            });
        }
        List<Future<String>> futures = executorService.invokeAll(listCall);
        List<String> result = new ArrayList<>();
        futures.forEach(task -> {
            while (true){
                if (task.isDone()){
                    try {
                        result.add(task.get());
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    }finally {
                        break;
                    }

                }
            }
        });
        executorService.shutdown();
        List<String> collect = result.stream().sorted().collect(Collectors.toList());
        for (String s : collect) {
            System.out.println(s);
        }
    }

    public static String DateFormatStr(int second){
        local.set(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
        String format = local.get().format(1000*second);
            return format;
    }

}

1970-01-01 08:00:00
1970-01-01 08:00:01
1970-01-01 08:00:02
.....
1970-01-01 08:00:48
1970-01-01 08:00:49

ThreadLocal  解决变量共享

private final static ThreadLocal<SystemUser> userThreadLocal = new ThreadLocal<SystemUser>();
    
    @Autowired
    private SystemLogService systemLogService;

    /**
     * 设置当前用户
     * 
     * @author cary
     * @param user
     */
    public void setCurrentSystemUser(SystemUser user) {
        userThreadLocal.set(user);
    }
    /**
     * 从本地线程中取当前用户
     * 
     * @author cary
     * @return 系统用户
     */
    public SystemUser getCurrentSystemUser() {
        SystemUser user = userThreadLocal.get();
        if (null == user) {
            user = (SystemUser) SecurityUtils.getSubject().getPrincipal();
        }

        return user;
    }

避免每个线程还需要主动地去创建这个对象(如果还需要共享,也一并解决了参数来回传递的问题)换句话说就是,“如何优雅的解决:线程间隔离与线程内共享的问题”,而不是说用来解决乱七八糟的线程安全问题,所以说如果有些场景你需要线程隔离,那么考虑ThreadLocal,而不是你有了什么线程安全问题需要解决,然后求助于ThreadLocal,这不是一回事。

摘自:https://www.cnblogs.com/weiguo21/p/4755527.html

摘自:http://www.cnblogs.com/noteless/p/10373044.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal是一个Java类,用于在多线程环境下保存线程本地变量的副本。通过创建ThreadLocal对象,每个线程都可以拥有自己独立的变量副本,互不干扰。 使用ThreadLocal的过程如下: 1. 创建一个ThreadLocal对象,可以指定泛型类型。 2. 在每个线程中,通过ThreadLocal对象的`get()`方法获取当前线程的变量副本。如果当前线程没有设置过变量值,则会使用默认值进行初始化。 3. 在每个线程中,通过ThreadLocal对象的`set(value)`方法设置当前线程的变量值。 4. 在每个线程中,通过ThreadLocal对象的`remove()`方法移除当前线程的变量副本。 需要注意的是,ThreadLocal对象的生命周期与Thread对象的生命周期一样长。当ThreadLocal对象被垃圾回收时,关联的变量副本也会被回收。 在ThreadLocal内部,使用ThreadLocalMap来存储每个线程的变量副本。ThreadLocal的实例作为key,变量值作为value。ThreadLocalMap可以使用强引用或弱引用来引用ThreadLocal对象。如果使用强引用,当ThreadLocal对象被回收时,如果没有手动删除对应的变量副本,会导致内存泄漏。如果使用弱引用,当ThreadLocal对象被回收时,对应的变量副本也会被回收。 总结来说,ThreadLocal是一种线程本地变量,通过保存每个线程的变量副本,实现了多线程环境下的线程隔离。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ThreadLocal使用与原理](https://blog.csdn.net/qq_35190492/article/details/116431270)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [ThreadLocal使用详解](https://blog.csdn.net/LJJZJ/article/details/88763666)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值