在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。 但在有些情况下,synchronized不能保证多线程对共享变量的正确读写。例如有一个类变量,该类变量会被多个类的方法读写,当多线程操作该类的实例对象时,如果线程对类变量有读取、写入操作就会发生类变量读写错误,即便是在类方法前加上synchronized也无效,因为同一个线程在两次调用方法之间时锁是被释放的,这时其它线程可以访问对象的类方法,读取或修改类变量。 这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。
举例说明:
为了说明多线程访问对于类变量和ThreadLocal变量的影响,CountSvc 中设置了类变量count,使用时先创建CountSvc 的一个实例对象,然后产生多个线程,分别设置不同的count实例对象,然后再调用execute方法,读取count的值,看是否是set方法中写入的值。 这种场景类似web应用中多个请求线程携带不同查询条件对一个servlet实例的访问,然后servlet调用业务对象,并传入不同查询条件,最后要保证每个请求得到的结果是对应的查询条件的结果。
public class CountSvc {
private String count;
public CountSvc() {
}
public void execute() {
System.out.println("Thread " + Thread.currentThread().getId() + "execute of count is " + count);
}
public String getCount() {
return count;
}
public void setCount(String count) {
this.count = count;
System.out.println("Thread " + Thread.currentThread().getId() + "setSql of count is " + count);
}
}
public class Work extends Thread {
private CountSvc countSvc;
private String count;
public Work(CountSvc countSvc,String count) {
this.countSvc = countSvc;
this.count = count;
}
public void run() {
countSvc.setCount(count);
countSvc.execute();
}
}
public class My {
static String count="";
public static void main(String[] args) {
CountSvc qs = new CountSvc();
for (int k=0; k<10;k++){
count="count =" + k;
new Work(qs,count).start();
}
}
}
输出的结果
可以看到线程9和线程10的同一个对象Count输出的count和set的不一样
public class CountSvc {
private String count;
private static ThreadLocal sqlHolder;
public CountSvc() {
sqlHolder = new ThreadLocal();
}
public void execute() {
System.out.println("Thread " + Thread.currentThread().getId() + "execute of count is " + count);
System.out.println("Thread " + Thread.currentThread().getId() +" Thread Local variable count is " + sqlHolder.get());
}
public String getCount() {
return count;
}
public void setCount(String count) {
this.count = count;
System.out.println("Thread " + Thread.currentThread().getId() + "setSql of count is " + count);
sqlHolder.set(count);
}
}
先创建一个CountSvc实例对象,然后创建若干线程来调用CountSvc的set和execute方法,每个线程传入的count都不一样,从运行结果可以看出count变量中值不能保证在execute中值和set设置的值一样,在 web应用中就表现为一个用户查询的结果不是自己的查询条件返回的结果,而是另一个用户查询条件的结果;而ThreadLocal中的值总是和set中设置的值一样,这样通过使用ThreadLocal获得了线程安全性。
如果一个对象要被多个线程访问,而该对象存在类变量被不同类方法读写,为获得线程安全,可以用ThreadLocal来替代类变量