见下页的示意图和代码,解释ThreadLocal的作用和目的:用于实现线程内数据共享,即对于相同的程序代码,多个模块在同一个线程中运行需要共享一份数据,而在另外线程中运行时又共享另外一份数据。

每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。
ThreadLocal应用场景
订单处理包含一系列操作:减少库存量,增加一条流水账,修改总账。这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理。如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则提交所有的操作。这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块中。
银行转账包含一系列操作:把转出的账户余额减少、把转入的账号余额增加。这2个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象。转入和转出操作的代码分别位于2个不同账户对象的方法。
例如Struts2的ActionContext,同一段代码被不同的线程调用运行时,改代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext()拿到的Context对象都不相同,对同一个线程来说,不管调用getContext()多少次和在哪个模块中调用getContext(),拿到的都是同一个。
- 实验案例:定义一个全局共享的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,接着各个线程调用另外其他多个类的方法,这多个类的方法中读取这个ThreadLocal变量。
- 实现对ThreadLocal变量的封装,让外界不要直接操作ThreadLocal变量
- 对基本类型的数据进行封装,这种应用相对很少见
- 对对象类型的数据进行封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象。
- 总结:一个ThreadLocal代表一个变量,故其中只能放一个数据,你有2个变量都要线程内共享,则要定义2个ThreadLocal对象,如果有一百个变量需要线程共享呢?那请先定义一个对象来装这一百个变量,然后在ThreadLocal中存储这一个对象。
- 先用全局变量做实验,看第一个线程取到的不是第一个线程放入的数据,而是第二个线程放入的数据。
- 用map演示线程范围内共享数据的原理。
- 先在MyThreadLocalData中定义一个访问权限为public的ThreadLocal类型的变量x,直接对这个x对象进行读写操作。
- 将MyThreadLocalData类自身变成一个具有业务功能的对象,每个线程仅能有该类的一个实例对象,即对于不同的线程来说,MyThreadLocalData.getMyData()静态方法拿到的对象都不相同,但对于同一个线程来说,不管调用MyThreadLocalData.getMyData()多少次和在哪里调用,拿到的都是同一个MyThreadLocalData对象。
先将MyThreadLocalData对象封装成具有业务功能的对象,
然后设计getMyData()方法的定义,
最后定义getMyData()要操作的ThreadLocal变量和编写具体的代码。
创建3个线程,他们都访问了3个对象,第一个对象设置值,第二三个对象取值,同一个线程设置的值,只能被相同的线程获取。
1. 示例程序(❌)
import java.util.Random;
public class ThreadScopeShareData {
static Random random = new Random();
private static int data = 0;
public static void main(String[] args) {
for(int i = 0; i < 2; i++)
new Thread(new Runnable() {
@Override
public void run() {
data = random.nextInt();
System.out.println(Thread.currentThread().getName() + " has put data : " + data);
new A().get();
new B().get();
}
}).start();
}
static class A {
public int get() {
System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
static class B {
public int get() {
System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
}
结果:
Thread-0 has put data : -596965467
Thread-1 has put data : -1853114354
A from Thread-0 has put data : -1853114354
A from Thread-1 has put data : -1853114354
B from Thread-1 has put data : -1853114354
B from Thread-0 has put data : -1853114354
这结果明显不是想要的。应该2个线程在A、B逻辑中get()的数据分别不同才对。

2. 改进版(Map版本):
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
public class ThreadScopeShareData {
static Random random = new Random();
private static Map<Thread,Integer> threadData = new ConcurrentHashMap<>();
public static void main(String[] args) {
for(int i = 0; i < 2; i++)
new Thread(new Runnable() {
@Override
public void run() {
int data = random.nextInt();
System.out.println(Thread.currentThread().getName() + " has put data : " + data);
threadData.put(Thread.currentThread(), data);
new A().get();
new B().get();
}
}).start();
}
static class A {
public int get() {
int data = threadData.get(Thread.currentThread());
System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
static class B {
public int get() {
int data = threadData.get(Thread.currentThread());
System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
}
Thread-0 has put data : 431467914
Thread-1 has put data : 1116409612
A from Thread-0 has put data : 431467914
A from Thread-1 has put data : 1116409612
B from Thread-0 has put data : 431467914
B from Thread-1 has put data : 1116409612
3.改进版(ThreadLocal版本)
package com.everjiankang.unit1;
import java.util.Random;
public class ThreadLocalTest {
static Random random = new Random();
private static ThreadLocal<Integer> threadData = new ThreadLocal<>();
public static void main(String[] args) {
for(int i = 0; i < 2; i++)
new Thread(new Runnable() {
@Override
public void run() {
int data = random.nextInt();
System.out.println(Thread.currentThread().getName() + " has put data : " + data);
threadData.set(data); //放到本线程里
new A().get();
new B().get();
}
}).start();
}
static class A {
public int get() {
int data = threadData.get(); //当前线程所set的数据
System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
static class B {
public int get() {
int data = threadData.get();//当前线程所set的数据
System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
}
结果:
Thread-0 has put data : 687235319
Thread-1 has put data : 1082718931
A from Thread-0 has put data : 687235319
A from Thread-1 has put data : 1082718931
B from Thread-0 has put data : 687235319
B from Thread-1 has put data : 1082718931
4. ThreadLocal存储非基本类型
package com.everjiankang.unit1;
import java.util.Random;
public class ThreadLocalTest2 {
static Random random = new Random();
private static ThreadLocal<User> threadData = new ThreadLocal<>();
public static void main(String[] args) {
for(int i = 0; i < 2; i++)
new Thread(new Runnable() {
@Override
public void run() {
int data = random.nextInt();
User user = new User();
user.setName("xiaochao " + data);
user.setAge(data);
System.out.println(Thread.currentThread().getName() + " has put data : " + user);
System.out.println();
threadData.set(user); //放到本线程里
new A().get();
new B().get();
}
}).start();
}
static class A {
public User get() {
User data = threadData.get(); //当前线程所set的数据
System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
static class B {
public User get() {
User data = threadData.get();//当前线程所set的数据
System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
}
class User {
private String name;
private int age;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
@Override
public String toString() {return "User [name=" + name + ", age=" + age + "]";}
}
结果:
Thread-1 has put data : User [name=xiaochao 811252830, age=811252830]
Thread-0 has put data : User [name=xiaochao 1834330439, age=1834330439]
A from Thread-0 has put data : User [name=xiaochao 1834330439, age=1834330439]
A from Thread-1 has put data : User [name=xiaochao 811252830, age=811252830]
B from Thread-0 has put data : User [name=xiaochao 1834330439, age=1834330439]
B from Thread-1 has put data : User [name=xiaochao 811252830, age=811252830]
改进1:单例模式获取User(❌)
单例无论多少个线程请求,都只有一个对象,所以无法实现每个线程一个实例
package com.everjiankang.unit1;
import java.util.Random;
public class ThreadLocalTest2 {
static Random random = new Random();
private static ThreadLocal<User2> threadData = new ThreadLocal<>();
public static void main(String[] args) {
for(int i = 0; i < 2; i++)
new Thread(new Runnable() {
@Override
public void run() {
int data = random.nextInt();
User2 user = User2.getInstance();
user.setName("xiaochao " + data);
user.setAge(data);
System.out.println(Thread.currentThread().getName() + " has put data : " + user);
System.out.println();
threadData.set(user); //放到本线程里
new A().get();
new B().get();
}
}).start();
}
static class A {
public User2 get() {
User2 data = threadData.get(); //当前线程所set的数据
System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
static class B {
public User2 get() {
User2 data = threadData.get();//当前线程所set的数据
System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
}
class User2 {
private User2() {}
private static User2 user = null;//new User();
public static synchronized User2 getInstance() {
if(user == null) {
user = new User2();
}
return user;
}
private String name;
private int age;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
@Override
public String toString() {return "User2 [name=" + name + ", age=" + age + "]";}
}
结果:
Thread-0 has put data : User2 [name=xiaochao 1219775690, age=1219775690]
Thread-1 has put data : User2 [name=xiaochao 1219775690, age=1219775690]
A from Thread-1 has put data : User2 [name=xiaochao 1219775690, age=1219775690]
A from Thread-0 has put data : User2 [name=xiaochao 1219775690, age=1219775690]
B from Thread-1 has put data : User2 [name=xiaochao 1219775690, age=1219775690]
B from Thread-0 has put data : User2 [name=xiaochao 1219775690, age=1219775690]
继续改进2:ThreadLocal形式的单例,更优雅
package com.everjiankang.unit1;
import java.util.Random;
public class ThreadLocalTest2 {
static Random random = new Random();
public static void main(String[] args) {
for(int i = 0; i < 2; i++)
new Thread(new Runnable() {
@Override
public void run() {
int data = random.nextInt();
User.getInstance().setName("xiaochao " + data);
User.getInstance().setAge(data);
System.out.println(Thread.currentThread().getName() + " has put data : " + User.getInstance());
System.out.println();
new A().get();
new B().get();
}
}).start();
}
static class A {
public User get() {
User data = User.getInstance(); //当前线程所set的数据
System.out.println("A from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
static class B {
public User get() {
User data = User.getInstance();//当前线程所set的数据
System.out.println("B from " + Thread.currentThread().getName() + " has put data : " + data);
return data;
}
}
}
/** ThreadLocal只在这里封装,更优雅,单例*/
class User {
private User() {}
private static ThreadLocal<User> map = new ThreadLocal<User>();//new User();
/**实现了ThreadLocal级别的单例*/
public static User getInstance() {
if(map.get() == null) {
map.set(new User());
}
return map.get();
}
private String name;
private int age;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
@Override
public String toString() {return "User [name=" + name + ", age=" + age + "]";}
}
网友评论