在java多线程中,为保证可变数据线程安全一种方式是使用同步的形式,另外一种方式是控制数据不被多线程共享--这种数据控制方式叫做数据线程封闭。数据的线程封闭通常使用局部变量(即数据属于线程私有)或者ThreadLocal(ThreadLocal对象原则上设计其包装的数据是每个线程对应一个值,即通常所说每个线程保存一个ThreadLocal副本)。根据自己理解的谈谈ThreadLocal的一般用法和不合理用法。
一、ThreadLocal一般用法
1、 使用getXXX()方法进行ThreadLocal<T> 类型T的初始化工作,ThreadLocal对象本身是所有线程共享的,只有T的对象是在启用线程或者使用ThreadLocal时候初始化的。
getXXX()方法就是在第一次线程使用ThreadLocal的时候进行初始化。其步骤是:
a. 在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。
b. 在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。
c. 在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。
上代码:
package cn.com.chp3.threadlocal;
import java.util.Random;
public class ThreadLocalDemo {
private static final ThreadLocal<Apple> threadlocal = new ThreadLocal<Apple>();
public Apple getApple(){
Apple apple = threadlocal.get();
if(apple == null){
Random random = new Random();
int index = random.nextInt(5);
apple = new Apple();
apple.setColor(index);
threadlocal.set(apple);
}
return apple;
}
public void displayThreadApple(){
int index = 10;
while(index > 0){
String currentThreadName = Thread.currentThread().getName();
Apple apple = getApple();
System.out.println(currentThreadName + " is running: " + "[ThreadLocal: " + threadlocal + "][apple: " + apple + "] [color: " + apple.getColor() + "]");
index--;
Thread.yield();
}
}
}
package cn.com.chp3.threadlocal;
public class ThreadLocalTest implements Runnable{
public final static ThreadLocalDemo td = new ThreadLocalDemo();
public static void main(String[] args) {
ThreadLocalTest test = new ThreadLocalTest();
td.displayThreadApple();
Thread th1 = new Thread(test, "threadA");
Thread th2 = new Thread(test, "threadB");
th1.start();
th2.start();
}
@Override
public void run() {
td.displayThreadApple();
}
}
package cn.com.chp3.threadlocal;
public class Apple {
private int color;
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
}
输出结果(结果只列出三行输出,不同线程输出同一个ThreadLocal实例,不同Apple实例,不同Apple颜色):
main is running: [ThreadLocal: java.lang.ThreadLocal@ca0b6][apple: cn.com.chp3.threadlocal.Apple@10b30a7] [color: 0]
threadA is running: [ThreadLocal: java.lang.ThreadLocal@ca0b6][apple: cn.com.chp3.threadlocal.Apple@69b332] [color: 2]
threadB is running: [ThreadLocal: java.lang.ThreadLocal@ca0b6][apple: cn.com.chp3.threadlocal.Apple@530daa] [color: 1]
2、重写ThreadLocal的initialValue()方法进行初始化,代码直接使用API里面提供的源码做记录。
import java.util.concurrent.atomic.AtomicInteger;
public class UniqueThreadIdGenerator {
private static final AtomicInteger uniqueId = new AtomicInteger(0);
private static final ThreadLocal < Integer > uniqueNum =
new ThreadLocal < Integer > () {
@Override protected Integer initialValue() {
return uniqueId.getAndIncrement();
}
};
public static int getCurrentThreadId() {
return uniqueId.get();
}
}
二、个人认为是ThreadLocal错误使用或者违反ThreadLocal设计原则的方法。{只是在验证ThreadLocal特性的过程中想出来的,也许使用过程中很少出现这种问题}
1、只在主线程中给ThreadLocal进行了初始化,其他线程中没有进行初始化。{只要按照推荐ThreadLocal两种初始化方法进行设计出现这种情况概率为零}
package cn.com.chp3.threadlocal;
import java.util.Random;
public class FailThreadLocalDemo {
private static final Apple apple;
private static final ThreadLocal<Apple> threadlocal = new ThreadLocal<Apple>();
static{
apple = new Apple();
threadlocal.set(apple);//仅是对主线程的ThreadLocal的值进行初始化
}
//getApple()方法没有进行初始化,而是直接取threadLocal的值,当线程进入时ThreadLocal还未来得及初始化
public Apple getApple(){
Apple apple = threadlocal.get();
if(apple == null){
String currentThreadName = Thread.currentThread().getName();
throw new RuntimeException(currentThreadName + ": 空指针错误!");
}
return apple;
}
public void displayThreadApple(){
int index = 10;
while(index > 0){
String currentThreadName = Thread.currentThread().getName();
Apple apple = getApple();
System.out.println(currentThreadName + " is running: " + "[ThreadLocal: " + threadlocal + "][apple: " + apple + "] [color: " + apple.getColor() + "]");
index--;
Thread.yield();
}
}
}
package cn.com.chp3.threadlocal;
public class FailThreadLocalTest implements Runnable{
public static final FailThreadLocalDemo td = new FailThreadLocalDemo();
public static void main(String[] args) {
FailThreadLocalTest test = new FailThreadLocalTest();
td.displayThreadApple();
Thread th1 = new Thread(test, "threadA");
Thread th2 = new Thread(test, "threadB");
th1.start();
th2.start();
}
@Override
public void run() {
td.displayThreadApple();
}
}
运行结果(主线程正常打印出值,A,B两个线程报空指针):
main is running: [ThreadLocal: java.lang.ThreadLocal@1fb8ee3][apple: cn.com.chp3.threadlocal.Apple@61de33] [color: 0]
Exception in thread "threadA" java.lang.RuntimeException: threadA: 空指针错误!
at cn.com.chp3.threadlocal.FailThreadLocalDemo.getApple(FailThreadLocalDemo.java:28)
at cn.com.chp3.threadlocal.FailThreadLocalDemo.displayThreadApple(FailThreadLocalDemo.java:37)
at cn.com.chp3.threadlocal.FailThreadLocalTest.run(FailThreadLocalTest.java:18)
at java.lang.Thread.run(Unknown Source)
Exception in thread "threadB" java.lang.RuntimeException: threadB: 空指针错误!
at cn.com.chp3.threadlocal.FailThreadLocalDemo.getApple(FailThreadLocalDemo.java:28)
at cn.com.chp3.threadlocal.FailThreadLocalDemo.displayThreadApple(FailThreadLocalDemo.java:37)
at cn.com.chp3.threadlocal.FailThreadLocalTest.run(FailThreadLocalTest.java:18)
at java.lang.Thread.run(Unknown Source)
2、进行了初始化,但是初始化ThreadLocal<T>时各个线程的T类型实例引用同一个实例,这样违反了每个线程保存一个ThreadLocal副本的设计思想。
package cn.com.chp3.threadlocal;
import java.util.concurrent.ThreadPoolExecutor;
public class MisUseThreadLocal implements Runnable{
private static final Apple apple = new Apple();
private static final ThreadLocal<Apple> threadlocal = new ThreadLocal<Apple>(){
public Apple initialValue() {
return apple;
}
};
@Override
public void run() {
displayThreadApple();
}
public Apple getApple(){
Apple apple = threadlocal.get();
if(apple == null){
String currentThreadName = Thread.currentThread().getName();
throw new RuntimeException(currentThreadName + ": 空指针错误!");
}
return apple;
}
public void displayThreadApple(){
int index = 10;
while(index > 0){
String currentThreadName = Thread.currentThread().getName();
Apple apple = getApple();
System.out.println(currentThreadName + " is running: " + "[ThreadLocal: " + threadlocal + "][apple: " + apple + "] [color: " + apple.getColor() + "]");
index--;
Thread.yield();
}
}
public static void main(String[] args) {
MisUseThreadLocal test = new MisUseThreadLocal();
test.displayThreadApple();
Thread th1 = new Thread(test, "threadA");
Thread th2 = new Thread(test, "threadB");
th1.start();
th2.start();
}
}
运行结果(三个线程运行结果出线程名不同外其他都相同):
main is running: [ThreadLocal: cn.com.chp3.threadlocal.MisUseThreadLocal$1@1fb8ee3][apple: cn.com.chp3.threadlocal.Apple@61de33] [color: 0]
threadA is running: [ThreadLocal: cn.com.chp3.threadlocal.MisUseThreadLocal$1@1fb8ee3][apple: cn.com.chp3.threadlocal.Apple@61de33] [color: 0]
threadB is running: [ThreadLocal: cn.com.chp3.threadlocal.MisUseThreadLocal$1@1fb8ee3][apple: cn.com.chp3.threadlocal.Apple@61de33] [color: 0]
三、总结
ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。
四、推荐
《主题:正确理解ThreadLocal》:http://www.iteye.com/topic/103804 对源码分析部分比较好理解。