ThredLocal不是一个线程,而是一个线程的本地化对象,当工作于多线程的对象用ThreadLocal维护变量(即共享资源)时,ThreadLocal会为每个使用该变量(即访问共享资源)的线程创建一个变量副本,每个线程操作的是ThreadLocal提供的变量副本,不会影响到其他线程,从而解决了共享资源在并发访问时带来的线程安全问题;
这个变量副本从线程的角度来说,就像是线程的一个本地变量或叫局部变量;
ThreadLocal的接口方法:
1 public void set(Object value);
设置当前线程的线程局部变量的值
2 public Object get();
返回当前线程的线程局部变量的值
3 public void remove();
将当前线程的线程局部变量的值删除,目的是较少内存占用
4 protected Object initialValue();
获取当前线程的线程局部变量的初始值,该方法是一个延迟调用方法,只有线程第一次调用set()或get()方法时,该方法才会被调用,且仅执行一次;
ThreadLocal如何做到为每个线程都维护一份独立的变量副本呢?
ThreadLocal类有一个map属性,map里面key存放当前线程对象,value存放线程局部变量副本,用线程对象做key来做区分,这样就能为每一个线程都提供一份独立的线程局部变量副本了
下面是一个简单的ThreadLocal类的实现:
/**
* TheadLocal类的简单实现
*/
class SimpleThreadLocal {
private Map<Thread, Object> map = Collections
.synchronizedMap(new HashMap<Thread, Object>());
// 设置当前线程的线程局部变量
public void set(Object value) {
map.put(Thread.currentThread(),value);
}
// 获取当前线程的线程局部变量
public Object get() {
Object value_=map.get(Thread.currentThread());
//未维护当前线程存储线程局部变量
if(value_==null&&!map.containsKey(Thread.currentThread())){
value_=initialValue();
//完成维护
map.put(Thread.currentThread(), value_);
}
return value_;
}
// 删除当前线程的本地局部变量
public void remove() {
map.remove(Thread.currentThread());
}
// 获取当前线程的线程局部变量的初始值 默认赋空值
protected Object initialValue() {
return null;
}
}
ThreadLocal实例:
共享资源类:
/**
* 共享对象类
*
* @author zhangjian_java
*
*/
public class SequenceNum {
//把共享资源通过ThreadLocal的initialVlue方法封装进ThreadLocal中 使ThreadLocal为每一个线程都提供一份独立的变量副本,从而解决了多线程环境下相同数据访问冲突问题;
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
public Integer initialValue() {
// 设置线程局部变量的初始值
return 9;
}
};
public Integer getNextNum() {
// 改变线程的局部变量的值 ---此处在模拟改变当前线程的全局共享资源副本
threadLocal.set(threadLocal.get() + 1);
// 获取更新后的变量副本
return threadLocal.get();
}
}
线程类:
/**
* 线程类
*
* @author zhangjian_java
*
*/
public class MyThread extends Thread {
// 有一个共享资源对象作为属性
private SequenceNum sequenceNum;
// 含参构造器
public MyThread(SequenceNum sequenceNum) {
this.sequenceNum = sequenceNum;
}
// 重写父类的run方法
// 在当前线程中操作共享资源,模拟多线程对共享资源的并发访问
public void run() {
for (int i = 0; i <= 3; i++) {
System.out.println("线程:" + Thread.currentThread().getName()
+ "当前线程的变量副本:" + sequenceNum.getNextNum());
}
}
}
测试类:
@Test
public void testThreadLocal() {
// 创建一个共享资源类
SequenceNum sequenceNum = new SequenceNum();
// 创建多个线程 多个线程之间共享同一资源
MyThread myThread1 = new MyThread(sequenceNum);
// 创建多个线程
MyThread myThread2 = new MyThread(sequenceNum);
// 创建多个线程
MyThread myThread3 = new MyThread(sequenceNum);
// 启动多线程
myThread1.start();
myThread2.start();
myThread3.start();
}
结果:
线程:Thread-0当前线程的变量副本:10
线程:Thread-1当前线程的变量副本:10
线程:Thread-1当前线程的变量副本:11
线程:Thread-1当前线程的变量副本:12
线程:Thread-1当前线程的变量副本:13
线程:Thread-0当前线程的变量副本:11
线程:Thread-0当前线程的变量副本:12
线程:Thread-0当前线程的变量副本:13
线程:Thread-2当前线程的变量副本:10
线程:Thread-2当前线程的变量副本:11
线程:Thread-2当前线程的变量副本:12
线程:Thread-2当前线程的变量副本:13
三个线程虽然并发的访问同一资源sequenceNum,但各个线程均有条不紊产生独立的序列号,没有对其他造成影响,
说明ThreadLoca为每一个当前线程提供一个独立的共享资源副本;
ThreadLocal和Thread同步机制的比较:
两者都是为了解决多线程下相同变量的访问冲突问题,
ThreadLocal:
提供共享资源副本,保证不破坏原数据,同一时间上允许有多个线程访问;
把共享资源通过ThreadLocal的initialVlue方法封装进ThreadLocal中 使ThreadLocal为每一个线程都提供一份独立的变量副本,从而解决了多线程环境下相同数据访问冲突问题;
Thread的同步机制:
从访问时间点控制访问数量为1;
使用对象的锁机制,使同一时间内只有一个线程访问共享资源;
ThreadLocal以空间换时间,访问并行化,对象独享化,为每一个线程都提供一份独立的变量副本,多个线程可以同时访问而互不影响;
Thread同步机制以时间换空间,访问串行化,对象共享化,仅提供一份变量,不同的线程排队访问;