ThreadLocal设计模式

简介:
ThreadLocal设计模式使用的也很频繁,会经常在各大框架找到它们的踪影,如struts2以及最近正在看的SpringAOP等。 

ThreadLocal设计模式也有很多误解,我的理解是 
(1)ThreadLocal所操作的数据是线程间不共享的。它不是用来解决多个线程竞争同一资源的多线程问题。 
(2)ThreadLocal所操作的数据主要用于线程内共享数据,可以避免同一线程内函数间的传参数问题。 
ThreadLocal更像是一个操作线程数据的工具类,哪个线程调用它,它就操作哪个线程的数据。 

案例如下: 
案例一:ThreadLocal所操作的数据是线程间不共享的,是线程间互不干扰的
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.lg.design.threadlocal;
 
public class ThreadLocalTest {
 
     public static void main(String[] args){
         test1();
     }
     private static void test1(){
         Thread t1= new Thread(){
 
             @Override
             public void run() {
                 System.out.println( this .getName()+ "开始值:" +ThreadUtil.getName());
                 ThreadUtil.setName( "线程0" );
                 sleepTime( 1000 * 10 );
                 System.out.println( this .getName()+ "设定后:" +ThreadUtil.getName());
             }
             
         };
         Thread t2= new Thread(){
 
             @Override
             public void run() {
                 sleepTime( 1000 * 5 );
                 System.out.println( this .getName()+ "开始值:" +ThreadUtil.getName());
                 ThreadUtil.setName( "线程1" );
                 System.out.println( this .getName()+ "设定后:" +ThreadUtil.getName());
             }
             
         };
         t1.start();
         t2.start();
     }
     
     private static void sleepTime( long millis){
         try {
             Thread.sleep(millis);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }
 
}

其中ThreadUtil就是用ThreadLocal来实现的,ThreadLocal完全可以这样理解,它就是操作线程数据的工具类,哪个线程调用它的get或set方法,它就会操作调用它的线程中的数据,待会给出源码说明。如下: 
?
1
2
3
4
5
6
7
8
9
10
11
12
public class ThreadUtil {
 
     private static ThreadLocal<String> nameLocal= new ThreadLocal<String>();
     
     public static String getName(){
         return nameLocal.get();
     }
     
     public static void setName(String name){
         nameLocal.set(name);
     }
}

运行结果为: 
?
1
2
3
4
Thread- 0 开始值: null
Thread- 1 开始值: null
Thread- 1 设定后:线程 1
Thread- 0 设定后:线程 0

分析过程如下: 
(1)暂时可以理解为每个线程都有一个map集合,用于存放数据。首先是t1线程调用ThreadUtil.getName(),ThreadUtil.getName()即nameLocal.get()会去调用它的线程的map集合中取数据,即会从t1线程中的map集合中取数据,key为nameLocal对象。由于之前未存数据所以返回为null。 
(2)紧接着t1调用ThreadUtil.setName("线程0")即nameLocal.set(name)。此时调用nameLocal.set(name)方法的线程是t1,所以nameLocal仍然是将数据存进t1的map集合中,key为nameLocal对象,然后t1睡眠10s。 
(3)t2刚开始睡眠5s,然后调用ThreadUtil.getName()即nameLocal.get(),nameLocal会去调用它的线程的map集合中取数据,即从t2线程的map集合中取数据,由于之前未存,所以仍然为null。 
(4)t2开始调用ThreadUtil.setName("线程1")即nameLocal.setName("线程1");,nameLocal仍然将这个值设置到调用它的线程的map集合中,即会将这个数据放到t2线程的map集合中,key为nameLocal对象。 
(5)然后是t2线程调用ThreadUtil.getName(),即nameLocal会从t2线程的map集合中取数据,key为nameLocal对象,然后就取到了步骤4所设置的"线程1"。 
(6)然后是t1线程调用ThreadUtil.getName(),即nameLocal会从t1线程的map集合中取数据,key为nameLocal对象,然后就取到了步骤2所设置的"线程0"。 
上面一直在强调ThreadLocal对象一直是在操作调用它的线程的map数据,谁调用ThreadLocal对象的get或set方法,ThreadLocal对象就会去操作调用它的线程的map数据。这个概念一定要根深蒂固。 

从上面的过程中,可以看到,ThreadLocal对象所操作的数据是线程所独有的数据,虽然两个线程使用的是同一个ThreadLocal对象,但ThreadLocal对象所操作的数据是线程间根本不共享的,互不干扰的。所以ThreadLocal模式就不是用于多个线程竞争同一资源的情况。 

案例二: 
ThreadLocal的作用:用于实现线程内共享数据,注意是线程内共享数据,不是线程间共享数据。
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ThreadLocalTest {
 
     public static void main(String[] args){
         test2();
     }
     
     private static void test2(){
         Thread t1= new Thread(){
 
             @Override
             public void run() {
                 fun1();
                 fun2();
             }
 
             private void fun2() {
                 System.out.println( "在函数2中获取到当前线程的name数据为:" +ThreadUtil.getName());
             }
 
             private void fun1() {
                 ThreadUtil.setName( "my own name" );
                 System.out.println( "函数1设置当前线程的name数据为:my own name" );
             }
             
         };
         t1.start();
     }
}

运行结果为: 
?
1
2
函数 1 设置当前线程的name数据为:my own name
在函数 2 中获取到当前线程的name数据为:my own name

分析如下: 
(1)fun1()和fun2()都在t1线程内执行,在fun1中调用ThreadUtil.setName("my own name"),则是向调用它的t1线程的map集合中存放数据,key为nameLocal。 
(2)fun2函数调用ThreadUtil.getName(),则是从调用它的t1线程的map集合中获取数据,key为nameLocal,所以就可以取到fun1中设置的数据 

综上所述,通过ThreadLocal对象,把线程内要共享的数据存进该线程的map集合中,方便随时获取,实现共享。不然的话,要想实现数据的共享,则需要fun1将数据作为参数传给fun2,这个例子太简单,然而实际情况是某个线程中几十个函数嵌套调用,如果不将线程内共享数据放进该线程的map集合中,而是将共享数据作为参数传递来传递去,将会一片混乱,杂乱无章,有些函数需要这些参数,有些函数不需要。采用ThreadLocal模式,将数据放进线程的map集合中,想要获取时随时可以通过ThreadLocal对象来获取,方便快捷。 

这就是ThreadLocal模式。下面源码来说明,ThreadLocal对象操作的是调用它的线程。 
这就需要看下它的get和set方法: 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public T get() {
       //重点1
         Thread t = Thread.currentThread();
      //重点2
         ThreadLocalMap map = getMap(t);
         if (map != null ) {
             //重点3
             ThreadLocalMap.Entry e = map.getEntry( this );
             if (e != null )
                 return (T)e.value;
         }
       //重点4
         return setInitialValue();
     }

(1)首先就是获取当前调用ThreadLocal对象的线程。如t1线程调用ThreadLocal对象的get方法,此时的Thread t就是t1. 
(2)Thread类拥有这样一个属性 ThreadLocal.ThreadLocalMap threadLocals = null; 
getMap(t)就是获取t线程的threadLocals,如下:
 
?
1
2
3
ThreadLocalMap getMap(Thread t) {
         return t.threadLocals;
     }

当线程中的ThreadLocal.ThreadLocalMap threadLocals不为空,则以this即当前的ThreadLocal对象作为key来获取对应的value。 
当线程中的ThreadLocal.ThreadLocalMap threadLocals为空时,则会初始化: 

?
1
2
3
4
5
6
7
8
9
10
private T setInitialValue() {
         T value = initialValue();
         Thread t = Thread.currentThread();
         ThreadLocalMap map = getMap(t);
         if (map != null )
             map.set( this , value);
         else
             createMap(t, value);
         return value;
     }

通过initialValue()来获取初始值,默认是null,这个过程也很清晰明了。就是判断当前线程中ThreadLocalMap是否为空,若为空则创建一个,同时将初始值设置进ThreadLocalMap集合中,key就是ThreadLocal对象本身。其中的initialValue()如下: 
?
1
2
3
protected T initialValue() {
         return null ;
     }

即我们可以重写该方法来设置一个初始值。 

ThreadLocal的set方法:
 
?
1
2
3
4
5
6
7
8
public void set(T value) {
         Thread t = Thread.currentThread();
         ThreadLocalMap map = getMap(t);
         if (map != null )
             map.set( this , value);
         else
             createMap(t, value);
     }

也很清晰明了,就是获取当期线程的ThreadLocalMap,然后向其中存放数据,key为ThreadLocal对象本身。 

这就是ThreadLocal设计模式。

相关文章
|
6月前
|
设计模式 安全 Java
最简单的设计模式是单例?
单例模式可以说是Java中最简单的设计模式,但同时也是技术面试中频率极高的面试题。因为它不仅涉及到设计模式,还包括了关于线程安全、内存模型、类加载等机制。所以说它是最简单的吗?
80 3
最简单的设计模式是单例?
|
1月前
|
设计模式 安全 Java
C# 一分钟浅谈:设计模式之单例模式
【10月更文挑战第9天】单例模式是软件开发中最常用的设计模式之一,旨在确保一个类只有一个实例,并提供一个全局访问点。本文介绍了单例模式的基本概念、实现方式(包括饿汉式、懒汉式和使用 `Lazy&lt;T&gt;` 的方法)、常见问题(如多线程和序列化问题)及其解决方案,并通过代码示例详细说明了这些内容。希望本文能帮助你在实际开发中更好地应用单例模式,提高代码质量和可维护性。
31 1
|
设计模式 安全 Java
【设计模式】单例设计模式
1、前言 单例模式是一种设计模式,它确保一个类只能创建一个实例,并提供一种全局访问这个实例的方式。在Java中,单例模式可以通过多种方式来实现,其中最常见的是使用私有构造函数和静态方法实现
56 0
|
5月前
|
设计模式 消息中间件 SQL
设计模式面试题-单例模式【JavaPub版】
设计模式面试题-单例模式【JavaPub版】
29 0
|
设计模式 安全 Java
【设计模式】——你真的会单例吗?
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
|
设计模式 机器学习/深度学习 前端开发
设计模式-单例
深度学习「设计模式」中的「单例模式」,并通过 javascript 实现单例模式;分析单例模式的使用场景
85 0
|
设计模式 存储 安全
设计模式-单例设计模式
单例设计模式是指,单例对象的类,只允许实例一个对象,这样,在jvm堆内存中,就只开辟了一块空间来存储这个对象,这种设计的优点是避免了内存中空间的浪费,避免频繁的创建销毁对象,为整个系统提供一个全局的访问的。打比方,windows桌面,我们已经点开了一个回收站,当我们再点击一次回收站的时候,不会再弹出一个回收站的弹窗。
66 0
|
设计模式 安全 Java
23种设计模式——单例模式
单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。
70 0
23种设计模式——单例模式
|
设计模式 安全 Java
设计模式-12-单例模式
单例模式(Singleton Pattern)负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。也属于创建型模式。
62 0
|
设计模式 存储 缓存
设计模式之单例
设计模式之单例
148 0
设计模式之单例