자바 ThreadLocal 사용 사례 상세 설명

10155 단어 자바ThreadLocal
본 고 는 동시 다발 환경 에서 스 레 드 가 안전 하지 않 은 Simple DateFormat 최적화 사례 를 통 해 ThreadLocal 을 이해 하도록 도와 준다.
최근 에 회사 프로젝트 를 정리 하면 서 비교적 나 쁜 부분 을 많이 발견 했다.예 를 들 어 다음 과 같은 것 이다.

public class DateUtil {

  private final static SimpleDateFormat sdfyhm = new SimpleDateFormat(
      "yyyyMMdd");
      
  public synchronized static Date parseymdhms(String source) {
    try {
      return sdfyhm.parse(source);
    } catch (ParseException e) {
      e.printStackTrace();
      return new Date();
    }
  }

}

우선 분석 해 보면:
이 함수 parseymdhms()는 synchronized 수식 을 사 용 했 습 니 다.이 동작 은 스 레 드 가 안전 하지 않 기 때문에 동기 화가 필요 합 니 다.스 레 드 가 안전 하지 않 아 도 Simple DateFormat 의 parse()방법 만 사용 할 수 있 습 니 다.다음 소스 코드 를 보 세 요.Simple DateFormat 에 전역 변수 가 있 습 니 다.

protected Calendar calendar;

Date parse() {

  calendar.clear();

 ... //       ,    calendar       

 calendar.getTime(); //   calendar   

}

이 clear()작업 은 라인 이 안전 하지 않 습 니 다.
또한 synchronized 키 워드 를 사용 하 는 것 은 성능 에 큰 영향 을 미 칩 니 다.특히 다 중 스 레 드 를 사용 할 때 parseymdhms 방법 을 호출 할 때마다 동기 화 판단 을 하고 동기 화 자체 의 비용 이 많이 들 기 때문에 불합리한 해결 방안 입 니 다.
개선 방법
스 레 드 가 안전 하지 않 은 것 은 다 중 스 레 드 가 공유 변 수 를 사용 해서 생 긴 것 입 니 다.그래서 여 기 는 ThreadLocal을 사용 하여 모든 스 레 드 에 복사 본 변 수 를 따로 만 들 고 코드 를 먼저 제시 한 다음 에 이러한 문 제 를 해결 하 는 원인 을 분석 합 니 다.

/**
 *      (   ThreadLocal  SimpleDateFormat,          common-lang)
 * @author Niu Li
 * @date 2016/11/19
 */
public class DateUtil {

  private static Map<String,ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>();

  private static Logger logger = LoggerFactory.getLogger(DateUtil.class);

  public final static String MDHMSS = "MMddHHmmssSSS";
  public final static String YMDHMS = "yyyyMMddHHmmss";
  public final static String YMDHMS_ = "yyyy-MM-dd HH:mm:ss";
  public final static String YMD = "yyyyMMdd";
  public final static String YMD_ = "yyyy-MM-dd";
  public final static String HMS = "HHmmss";

  /**
   *   map  key       sdf  
   * @param pattern map  key
   * @return    
   */
  private static SimpleDateFormat getSdf(final String pattern){
    ThreadLocal<SimpleDateFormat> sdfThread = sdfMap.get(pattern);
    if (sdfThread == null){
      //    ,  sdfMap   put   ,            
      synchronized (DateUtil.class){
        sdfThread = sdfMap.get(pattern);
        if (sdfThread == null){
          logger.debug("put new sdf of pattern " + pattern + " to map");
          sdfThread = new ThreadLocal<SimpleDateFormat>(){
            @Override
            protected SimpleDateFormat initialValue() {
              logger.debug("thread: " + Thread.currentThread() + " init pattern: " + pattern);
              return new SimpleDateFormat(pattern);
            }
          };
          sdfMap.put(pattern,sdfThread);
        }
      }
    }
    return sdfThread.get();
  }

  /**
   *     pattern    
   * @param date     date
   * @param pattern     
   * @return    date  
   */
  public static Date parseDate(String date,String pattern){
    if(date == null) {
      throw new IllegalArgumentException("The date must not be null");
    }
    try {
      return getSdf(pattern).parse(date);
    } catch (ParseException e) {
      e.printStackTrace();
      logger.error("        :"+pattern);
    }
    return null;
  }
  /**
   *     pattern     
   * @param date      date
   * @param pattern     
   * @return      
   */
  public static String formatDate(Date date,String pattern){
    if (date == null){
      throw new IllegalArgumentException("The date must not be null");
    }else {
      return getSdf(pattern).format(date);
    }
  }
}

테스트
주 스 레 드 에서 하 나 를 실행 하고,다른 두 개 는 하위 스 레 드 에서 실행 하 며,모두 같은 pattern 을 사용 합 니 다.

public static void main(String[] args) {
    DateUtil.formatDate(new Date(),MDHMSS);
    new Thread(()->{
      DateUtil.formatDate(new Date(),MDHMSS);
    }).start();
    new Thread(()->{
      DateUtil.formatDate(new Date(),MDHMSS);
    }).start();
  }
로그 분석

put new sdf of pattern MMddHHmmssSSS to map
thread: Thread[main,5,main] init pattern: MMddHHmmssSSS
thread: Thread[Thread-0,5,main] init pattern: MMddHHmmssSSS
thread: Thread[Thread-1,5,main] init pattern: MMddHHmmssSSS
분석 하 다.
sdfpap put 가 한 번 들 어간 것 을 알 수 있 습 니 다.Simple DateFormat 은 new 에 세 번 들 어 갔 습 니 다.코드 에 세 개의 스 레 드 가 있 기 때 문 입 니 다.그렇다면 왜 일 까요?
모든 스 레 드 Thread 에 대해 내부 에 ThreadLocal.ThreadLocalMap threadLocals 의 전역 변수 참조 가 있 습 니 다.ThreadLocal.ThreadLocalMap 에는 이 ThreadLocal 과 대응 하 는 value 가 저장 되 어 있 습 니 다.그림 은 천 마디 를 이 깁 니 다.구조 도 는 다음 과 같 습 니 다.

그럼 sdfpap 에 대해 서 는 구조 도 를 변경 하 였 습 니 다.

1.먼저 DateUtil.formatDate(new Date(),MDHMSS)를 처음 실행 합 니 다.

//     DateUtil.formatDate(new Date(),MDHMSS)  
  private static SimpleDateFormat getSdf(final String pattern){
    ThreadLocal<SimpleDateFormat> sdfThread = sdfMap.get(pattern);
    //   sdfThread null,  if  
    if (sdfThread == null){
      synchronized (DateUtil.class){
        sdfThread = sdfMap.get(pattern);
        //sdfThread   null,  if  
        if (sdfThread == null){
          //    
          logger.debug("put new sdf of pattern " + pattern + " to map");
          //  ThreadLocal  ,   initialValue  
          sdfThread = new ThreadLocal<SimpleDateFormat>(){
            @Override
            protected SimpleDateFormat initialValue() {
              logger.debug("thread: " + Thread.currentThread() + " init pattern: " + pattern);
              return new SimpleDateFormat(pattern);
            }
          };
          //    sdfMap
          sdfMap.put(pattern,sdfThread);
        }
      }
    }
    return sdfThread.get();
  }
이 럴 때 ThreadLocal 의 set 방법 을 사용 하지 않 았 습 니 다.그러면 값 은 어떻게 설정 되 어 있 습 니까?
sdfThread.get()의 실현 을 봐 야 합 니 다.

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
      ThreadLocalMap.Entry e = map.getEntry(this);
      if (e != null) {
        @SuppressWarnings("unchecked")
        T result = (T)e.value;
        return result;
      }
    }
    return setInitialValue();
  }
즉,값 이 존재 하지 않 을 때 setInitialValue()방법 을 사용 합 니 다.이 방법 은 initialValue()방법,즉 우리 가 덮어 쓰 는 방법 을 사용 합 니 다.
대응 로그 인쇄.

put new sdf of pattern MMddHHmmssSSS to map
thread: Thread[main,5,main] init pattern: MMddHHmmssSSS
2.두 번 째 부분 스 레 드 에서 DateUtil.formatDate(new Date(),MDHMSS)를 실행 합 니 다.

//         `DateUtil.formatDate(new Date(),MDHMSS);`
  private static SimpleDateFormat getSdf(final String pattern){
    ThreadLocal<SimpleDateFormat> sdfThread = sdfMap.get(pattern);
    //     sdfThread  null,  if 
    if (sdfThread == null){
      synchronized (DateUtil.class){
        sdfThread = sdfMap.get(pattern);
        if (sdfThread == null){
          logger.debug("put new sdf of pattern " + pattern + " to map");
          sdfThread = new ThreadLocal<SimpleDateFormat>(){
            @Override
            protected SimpleDateFormat initialValue() {
              logger.debug("thread: " + Thread.currentThread() + " init pattern: " + pattern);
              return new SimpleDateFormat(pattern);
            }
          };
          sdfMap.put(pattern,sdfThread);
        }
      }
    }
    //    sdfThread.get()  
    return sdfThread.get();
  }
sdfThread.get 분석()

//         `DateUtil.formatDate(new Date(),MDHMSS);`
  public T get() {
    Thread t = Thread.currentThread();//       
    ThreadLocalMap map = getMap(t);
    //       map null,  if 
    if (map != null) {
      ThreadLocalMap.Entry e = map.getEntry(this);
      if (e != null) {
        @SuppressWarnings("unchecked")
        T result = (T)e.value;
        return result;
      }
    }
    //       ,          initialValue()  
    return setInitialValue();
  }
대응 로그:
Thread[Thread-1,5,main] init pattern: MMddHHmmssSSS
총결산
어떤 장면 에서 ThreadLocal 을 사용 하기에 적합 합 니까?stackoverflow 에서 괜 찮 은 대답 이 나 왔 습 니 다.
When and how should I use a ThreadLocal variable?
One possible (and common) use is when you have some object that is not thread-safe, but you want to avoid synchronizing access to that object (I'm looking at you, SimpleDateFormat). Instead, give each thread its own instance of the object.
참조 코드:
Util-Demo
참고 자료:
https://github.com/nl101531/JavaWEB
알 기 쉬 운 학습 자바 ThreadLocal
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기