Java ThreadLocal介绍

       记录一下ThreadLocal的一些用法,以及由此引出的一个Hadoop bug。首先说一下ThreadLocal,通常在多线程编程方面我们更多使用的是Lock,Synchronized等等,ThreadLocal使用并不是很多。当年我入职淘宝时候其中的一道笔试题考查的就是ThreadLocal。 根据JDK中的描述,ThreadLocal提供了Thread本地变量。相对于ThreadLocal,其他变量能够被所有线程访问并且改写,需要通过同步机制来进行协调。而ThreadLocal是每个线程内部的一个私有变量,线程有个隐式的引用,线程之间互不干扰。简单的说就是ThreadLocal是通过空间换时间,每个线程对应了一个ThreadLocal的私有变量,只有这个线程自己使用,它的好处是不用关注线程之间的相互干扰,不需要加锁等,这样在大并发情况下是相当划算的,因为synchronized操作毕竟是相当损失性能的。

       但是,使用ThreadLocal的时候要注意ThreadLocal变量是线程独有的,如果通过ThreadLocal需改一些状态改变,一定要注意不要将这个线程之前的状态加入到这次操作中,导致错误。比如,下面的列子,我有10个线程,每个线程有一个Meta的ThreadLocal变量,我的本意是10个线程去设置状态,每个线程做1000次操作,其中有一次是Exception的操作,其它都是正确的操作。下面为代码:

       Meta类:

package com.dj;
 
 
public class Meta {
 
  private int number;
  private STATE state;
 
  public Meta() {
    number = 0;
    state = STATE.NORMAL;
  }
 
  private Meta(int number, STATE state) {
    this.number = number;
    this.state = state;
  }
 
  public int getNumber() {
    return number;
  }
 
  public void setNumber(int number) {
    this.number = number;
  }
 
  public STATE getState() {
    return state;
  }
 
  public void setState(STATE state) {
    this.state = state;
  }
 
  public static Meta spawn(Meta meta) {
    return new Meta(meta.number,meta.state);
  }
 
}

       STATE类:

package com.dj;
 
public enum STATE {
  NORMAL, EXCEPTION
};

       Process类。

package com.dj;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;
 
public class Process {
  public static final AtomicInteger GENERATOR = new AtomicInteger(0);
  public static final int TIMES =  1000;
  public static final int THREADS = 10;
  public static final int EXCEPTION_NUM = 10;
  public ArrayList<Meta> list = new ArrayList<Meta>();
 
 
  ThreadLocal<Meta> t = new ThreadLocal<Meta>() {
    protected Meta initialValue() {
      return new Meta();
    };
  };
 
  synchronized void process(STATE state) {
    t.get().setNumber(GENERATOR.getAndAdd(1));
    if(state==STATE.EXCEPTION) {
      t.get().setState(state);
    }
    list.add(Meta.spawn(t.get()));
  }
 
 
  public int stat() {
    Collections.sort(list, new Comparator<Meta>() {
 
      @Override
      public int compare(Meta o1, Meta o2) {
        return o1.getNumber() < o2.getNumber() ? -1 : 1; 
      }
 
    });
 
    int exception = 0;
    for(Meta meta: list) {
      if(meta.getState() == STATE.EXCEPTION)
        exception++;
    }
 
 
    System.out.println("Exception number should be " +
    EXCEPTION_NUM+" but the real number is "+exception);
    return exception;
  }
 
  public static void main(String[] args) {
    final Semaphore locks = new Semaphore(THREADS);
    Thread[] threads = new Thread[THREADS];
    final Process process = new Process();
    final AtomicInteger exSize = new AtomicInteger(0);
    for(Thread t : threads) {
      t = new Thread(new Runnable() {
 
        @Override
        public void run() {
          try {
            locks.acquire();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          Random r = new Random();
          int setException = r.nextInt(TIMES);
          exSize.addAndGet(TIMES-setException);
          for(int i=0 ; i<TIMES; i++) {
            if(i == setException)
              process.process(STATE.EXCEPTION);
            else 
              process.process(STATE.NORMAL);
          }
          locks.release();
        }
      });
      t.start();
    }
    int realNumber = 0;
    while(true) {
      if(locks.availablePermits() == THREADS) {
        realNumber = process.stat();
        break;
      }
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        break;
      }
    }
    System.out.println(realNumber+"  "+exSize);
  }
}

       运行时结果为:

Exception number should be 10 
but the real number is 4943 4943 4943

       这也很好解释,由于我们设置Exception操作之后没有重新设置状态,导致了异常状态有上千次。这其实也是近期我发现的Hadoop一个比较严重Bug的一个模拟。下一篇会讨论一下Hadoop的这个问题。