早在JDK的版本中就提供了javalangThreadLocal为解决多线程程序的并发问题提供了一种新的思路使用这个工具类可以很简洁地编写出优美的多线程程序
ThreadLocal很容易让人望文生义想当然地认为是一个本地线程其实ThreadLocal并不是一个Thread而是Thread的局部变量也许把它命名为ThreadLocalVariable更容易让人理解一些当使用ThreadLocal维护变量时Threadlocal为每个使用该变量的线程提供独立的变量副本所以每一个线程都可以独立地改变自己的副本而不会影响其他线程所独立的副本
从线程的角度看目标变量就是线程的本地变量这也是类名中Local所要表达的意思线程局部变量并不是Java的新发明很多语言(如IBM XL FORTRAN)在语法层面就提供了线程局部变量在Java中没有提供语言级支持而是变相地通过ThreadLocal的类提供支持
JDK以后提供了泛型支持ThreadLocal被定义为支持泛型
public class ThreadLocal<T> extends Object
T为线程局部变量的类型该类定义了个方法
)protected T initialValue()返回此线程局部变量的当前线程的初始值线程的第一次使用get()方法访问变量时将调用此方法但如果线程之前调用了set(T)方法则不会对该线程再调用initialValue方法通常此方法对每个线程最多调用一次但如果在调用get()后又调用了remove()则可能再次调用此方法
该实现返回null如果程序员希望线程局部变量具有null以外的值则必须为ThreadLocal创建子类并重写此方法通常将使用匿名内部类完成此操作
)public Tget()返回此线程局部变量的当前线程副本中的值如果变量没有用于当前线程的值则先将其初始化为调用initialValue方法返回的值
)public void Set(T value)将此线程局部变量的当前线程副本中的值设置为指定值大部分子类不需要重写此方法它们只依靠initialValue()方法来设置线程局部变量的值
)public void remove()移除此线程局部变量当前线程的值如果此线程局部变量随后被当前线程读取且这期间当前线程没有设置其值则将调用其initialValue()方法重新初始化其值这将导致在当前线程多次调用initialValue方法
下面是一个使用ThreadLocal的例子每个线程产生自己独立的序列号就是使用Threadlocal存储每个线程独立的版本号副本线程之间互不干扰
public class SequenceNumber {
//定义匿名内部类创建ThreadLocal的变量
private static ThreadLocal<Integer> seqNum=new ThreadLocal<Integer>(){
//覆盖初始化方法
public Integer initialValue(){
return ;
}
};
//下一个序列号
public int getNextNum(){
seqNumset(seqNumget()+)
return seqNumget()
}
private static class TestClient extends Thread{
private SequenceNumber sn;
public TestClient(SequenceNumber sn){
thissn=sn;
}
//产生序列号
public void run(){
for(int i=;i<;i++){
Systemoutprintln(Thread[+ThreadcurrentThread()getName()+
] sn[+sngetNextNum()+])
}
}
}
public static void main(String[] args) {
SequenceNumber seqNum=new SequenceNumber()
//三个线程产生各自的序列号
TestClient t=new TestClient(seqNum)
TestClient t=new TestClient(seqNum)
TestClient t=new TestClient(seqNum)
tstart()
tstart()
tstart()
}
}
public class SequenceNumber { //定义匿名内部类创建ThreadLocal的变量 private static ThreadLocal<Integer> seqNum=new ThreadLocal<Integer>(){ //覆盖初始化方法 public Integer initialValue(){ return ; } }; //下一个序列号 public int getNextNum(){ seqNumset(seqNumget()+) return seqNumget() } private static class TestClient extends Thread{ private SequenceNumber sn; public TestClient(SequenceNumber sn){ thissn=sn; } //产生序列号 public void run(){ for(int i=;i<;i++){ Systemoutprintln(Thread[+ThreadcurrentThread()getName()+ ] sn[+sngetNextNum()+]) } } } public static void main(String[] args) { SequenceNumber seqNum=new SequenceNumber() //三个线程产生各自的序列号 TestClient t=new TestClient(seqNum) TestClient t=new TestClient(seqNum) TestClient t=new TestClient(seqNum) tstart() tstart() tstart() } }
程序的运行结果如下
Java代码
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[]
Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[] Thread[Thread] sn[]
从运行结果可以看出使用了ThreadLocal后每个线程产生了独立的序列号没有相互干扰通常我们通过匿名内部类的方式定义ThreadLocal的子类提供初始的变量值
ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问沖突问题
在同步机制中通过对象的锁机制保证同一时间只有一个线程访问变量这时该变量是多个线程共享的使用同步机制要求程序缜密地分析什么时候对变量进行读写什么时候需要锁定某个对象时候时候释放对象锁等繁杂的问题程序设计和编写难度相对较大
而ThreadLocal则从另一个角度来解决多线程的并发访问ThreadLocal会为每一个线程提供一个独立的变量副本从而隔离了多个线程对数据的访问沖突因为每一个线程都拥有自己的变量副本从而也就没有必要对该变量进行同步了ThreadLocal提供了线程安全的共享对象在编写多线程代码时可以把不安全的变量封装进ThreadLocal
概况起来说对于多线程资源共享的问题同步机制采用了以时间换空间的方式而ThreadLocal采用了以空间换时间的方式前者仅提供了一份变量让不同的线程排队访问而后者为每一个线程都提供了一份变量因此可以同时访问而互不影响
需要注意的是ThreadLocal对象是一个本质上存在风险的工具应该在完全理解将要使用的线程模型之后再去使用ThreadLocal对象这就引出了线程池(thread pool)的问题线程池是一种线程重用技术有了线程池就不必为每个任务创建线的线程一个线程可能会多次使用用于这种环境的任何ThreadLocal对象包含的都是最后使用该线程的代码锁设置的状态而不是在开始执行新线程时所具有的未被初始化的状态
google_protectAndRun(render_adsjs::google_render_ad google_handleError google_render_ad)