JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的局部变量独立问题...
查看API我们可以查看ThreadLocal的定义与方法:
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
例如,以下类生成对每个线程唯一的局部标识符。线程 ID 是在第一次调用 UniqueThreadIdGenerator.getCurrentThreadId() 时分配的,在后续调用中不会更改。
构造方法摘要 | |
---|---|
ThreadLocal() 创建一个线程本地变量。 |
方法摘要 | |
---|---|
T | get() 返回此线程局部变量的当前线程副本中的值。 |
protected T | initialValue() 返回此线程局部变量的当前线程的“初始值”。 |
void | remove() 移除此线程局部变量当前线程的值。 |
void | set(T value) 将此线程局部变量的当前线程副本中的值设置为指定值。 |
remove()
移除此线程局部变量当前线程的值,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。如果泛型对象是Integer,可以定义内部内,重写initialValue() 返回0等Integer类型的数据!
ThreadLocal并非一个所谓的 "local thread (本地线程)",而是处理线程中的局部变量的.其可以理解为:"local thread variable(线程局部变量)"!
线程局部变量已经有很多语言使用了,并非是Java独创...很多语言(如IBM IBM XL FORTRAN)在语法层面就提供线程局部变量。
ThreadLocal的具体实现原理大致如下:
- package cn.vicky;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.Map;
- public class RealizeThreadLocal {
- private Map<String, Object> map = Collections.synchronizedMap(new HashMap<String, Object>());
- public void set(Object value) {
- map.put(Thread.currentThread().getName(), value);
- }
- public Object get() {
- String threadName = Thread.currentThread().getName();
- Object o = map.get(threadName);
- if (o == null && !map.containsKey(threadName)) {
- o = initialValue();
- map.put(threadName, o);
- }
- return o;
- }
- public void remove() {
- map.remove(Thread.currentThread().getName());
- }
- public Object initialValue() {
- return null;
- }
- }
在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。其实现原理大致如下:
- package cn.vicky;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.Map;
- public class RealizeThreadLocal2<T> {
- private Map<Long, T> map = Collections.synchronizedMap(new HashMap<Long, T>());
- public void set(T value) {
- map.put(Thread.currentThread().getId(), value);
- }
- public T get() {
- long threadID = Thread.currentThread().getId();
- T o = map.get(threadID);
- if (o == null && !map.containsKey(threadID)) {
- o = initialValue();
- map.put(threadID, o);
- }
- return o;
- }
- public void remove() {
- map.remove(Thread.currentThread().getName());
- }
- public T initialValue() {
- return null;
- }
- }
如何使用:
- package cn.vicky;
- public class SequenceNumber {
- // 通过匿名内部类覆盖RealizeThreadLocal2的initialValue()方法,根据泛型类型指定对应初始值
- private static RealizeThreadLocal2<Integer> seqNum = new RealizeThreadLocal2<Integer>() {
- @Override
- public Integer initialValue() {
- return 0;
- }
- };
- // 获取下一个序列值
- public int getNextNum() {
- seqNum.set(seqNum.get() + 1);
- return seqNum.get();
- }
- public static void main(String[] args) {
- SequenceNumber sn = new SequenceNumber();
- // 3个线程共享sn,各自产生序列号
- TestThread tt1 = new TestThread(sn);
- TestThread tt2 = new TestThread(sn);
- TestThread tt3 = new TestThread(sn);
- Thread t1 = new Thread(tt1);
- Thread t2 = new Thread(tt2);
- Thread t3 = new Thread(tt3);
- t1.start();
- t2.start();
- t3.start();
- }
- }
- package cn.vicky;
- public class TestThread implements Runnable {
- private SequenceNumber sn;
- public TestThread(SequenceNumber sn) {
- this.sn = sn;
- }
- public void run() {
- // 每个线程打出3个序列值
- for (int i = 0; i < 3; i++) {
- System.out.println("thread[" + Thread.currentThread().getName() + "] sn[" + sn.getNextNum() + "]");
- }
- }
- }
打印:
thread[Thread-0] sn[1]
thread[Thread-0] sn[2]
thread[Thread-0] sn[3]
thread[Thread-2] sn[1]
thread[Thread-2] sn[2]
thread[Thread-2] sn[3]
thread[Thread-1] sn[1]
thread[Thread-1] sn[2]
thread[Thread-1] sn[3]
TestThread tt1 = new TestThread(sn);
TestThread tt2 = new TestThread(sn);
TestThread tt3 = new TestThread(sn);
输出的结果信息,我们发现每个线程所产生的序号虽然都共享同一个SequenceNumber (sn) 实例,但它们并没有发生相互干扰的情况,而是各自产生独立的序列号,这是因为我们通过ThreadLocal为每一个线程提供了单独的副本!
让我们再看个简单的实例:
- package cn.vicky.chapt05;
- /**
- *
- * @author Vicky.H
- */
- public class ThreadLocalTest1 {
- /** 实现了每个线程的静态变量不同 **/
- private static ThreadLocal<String> val = new ThreadLocal<String>();
- public static class Thread1 extends Thread {
- private String name;
- public Thread1(String name) {
- this.name = name;
- }
- public void run() {
- System.out.println(name + "初始值:" + val.get());
- val.set("v[" + name + "]");
- System.out.println(name + "设置后值:" + val.get());
- }
- }
- public static class Thread2 extends Thread {
- private String name;
- public Thread2(String name) {
- this.name = name;
- }
- public void run() {
- System.out.println(name + "初始值:" + val.get());
- val.set("v[" + name + "]");
- System.out.println(name + "设置后值:" + val.get());
- }
- }
- public static void main(String[] args) {
- val.set("1");
- System.out.println("主程序中设置值:" + val.get());
- (new Thread1("A1")).start();
- (new Thread1("A")).start();
- (new Thread2("B1")).start();
- }
- //主程序中设置值:1
- //A1初始值:null
- //A1设置后值:v[A1]
- //B1初始值:null
- //A初始值:null
- //A设置后值:v[A]
- //B1设置后值:v[B1]
- //总结
- //ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。
- //ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
- //ThreadLocal不能使用原子类型,只能使用Object类型。
- //ThreadLocal的使用比synchronized要简单得多。
- //ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
- //Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
- //当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。
- }
- package cn.vicky.chapt05;
- /**
- *
- * @author Vicky.H
- */
- public class ThreadLocalTest2 {
- /** 实现了每个线程的静态变量不同 **/
- private static String val;
- public static class Thread1 extends Thread {
- private String name;
- public Thread1(String name) {
- this.name = name;
- }
- public void run() {
- System.out.println(name + "初始值:" + val);
- val = "v[" + name + "]";
- System.out.println(name + "设置后值:" + val);
- }
- }
- public static class Thread2 extends Thread {
- private String name;
- public Thread2(String name) {
- this.name = name;
- }
- public void run() {
- System.out.println(name + "初始值:" + val);
- val = "v[" + name + "]";
- System.out.println(name + "设置后值:" + val);
- }
- }
- public static void main(String[] args) {
- val = "1";
- System.out.println("主程序中设置值:" + val);
- (new Thread1("A1")).start();
- (new Thread1("A")).start();
- (new Thread2("B1")).start();
- }
- //主程序中设置值:1
- //A1初始值:1
- //A1设置后值:v[A1]
- //A初始值:v[A1]
- //A设置后值:v[A]
- //B1初始值:v[A]
- //B1设置后值:v[B1]
- }