我们可以使用ThreadLocal实现线程范围的共享变量。
ThreadLocal的作用和目的,用于实现线程内的数据共享,即对于相同的程序代码,多个模块在
同一个线程中运行时要共享一份数据,而在另外线程中运行又共享另外一份数据。
线程范围内共享数据的示意图:
每个线程调用全局ThreadLocal对象的set方法,对相当于往其内部的map中增加一条记录,key
分别是各自的线程,value是各自的set方法传进去的值。在线程结束是可以调用ThreadLocal.clear()方法,
这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。
ThreadLocal的应用场景:
(1)订单处理包括一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个
事务中完成,通常也在同一个线程中进行处理,例如累加公司收款的操作失败了,则以应该把前面
的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代
码分别位于不同的模块中。
(2)银行转账包含一系列操作:把转出账户的余额减少,把转入账户的余额增加,这两个操作要在同
一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别位于不同的账
户对象的方法。
(3)又比如,struts2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是
每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对于一个
线程来说,不管调用getContext方法多少次和在哪个模块中执行getContext方法,拿到的都已同一个。
我们接下来写一个线程范围内共享变量的实例:
首先我们先用两个线程获取同一个全局变量:
package cn.edu.hpu.test;
import java.util.Random;
public class ThreadTest6 {
private static int data=0;
private static Random dandom=new Random();
public static void main(String[] args) {
for(int i=0;i<2;i++){
new Thread(new Runnable(){
public void run() {
data=dandom.nextInt();
System.out.println(Thread.currentThread().getName()
+"放入数据:"+data);
new A().get();
new B().get();
}
}).start();
}
}
static class A{
public void get(){
System.out.println("A 从"+Thread.currentThread().getName()
+"中取的数据:"+data);
}
}
static class B{
public void get(){
System.out.println("B 从"+Thread.currentThread().getName()
+"中取的数据:"+data);
}
}
}
结果:
可以看到,A类和B类两次取得的数据均为线程第二次获取的随机值,而不是每一次执行的值,
所以没有做到线程内生成的数据共享的功能。
我们来改进一下这个类:
我们使用一个Map来储存每个线程生成的data数据,然后设置key为每个线程本身,
在取数据的时候使用key去取每个线程的data数据。
package cn.edu.hpu.test;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class ThreadTest6 {
private static Random dandom=new Random();
private static Map<Thread,Integer> threadData=new HashMap<Thread,Integer>();
public static void main(String[] args) {
for(int i=0;i<2;i++){
new Thread(new Runnable(){
public void run() {
int data=dandom.nextInt();
System.out.println(Thread.currentThread().getName()
+"放入数据:"+data);
threadData.put(Thread.currentThread(), data);
new A().get();
new B().get();
}
}).start();
}
}
static class A{
public void get(){
System.out.println("A 从"+Thread.currentThread().getName()
+"中取的数据:"+threadData.get(Thread.currentThread()));
}
}
static class B{
public void get(){
System.out.println("B 从"+Thread.currentThread().getName()
+"中取的数据:"+threadData.get(Thread.currentThread()));
}
}
}
结果:
可以看到,每一个线程内生成的data数据我们都已经获取。
这样做到了每个线程内的数据进行了共享。
这种操作实际上在日常开发中就有接触,例如我们使用JDBC连接数据库,
如果所有操作数据库的类拿的都是同一个Connection,事务开始和结束都是在每个类
各自的线程中执行的,如果有一个线程刚刚开始添加数据,然后准备再删除一些数据的时候,
另外一个线程的事务做了commit操作,这就有可能导致第一个线程的事务也被Commit。
而使用了类似上面的线程数据共享机制,每一个线程都有其独立的,不与其他线程混杂在一起
的独立数据,就可以保证线程正常运行。
使用JDK5的ThreadLocal类可以更方便的实现线程内数据共享,具体请看下次总结。