Java并发编程-ThreadLocal

        在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 对源码分析部分比较好理解。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值