이 기사는 동시 환경에서 안전하지 않은 SimpledateFormat 최적화 사례를 사용하여 모든 사람이 ThreadLocal을 이해하도록 도와줍니다.
최근에 나는 회사의 프로젝트를 분류했고 다음과 같은 많은 나쁜 것들이 있다는 것을 발견했습니다.
공개 클래스 dateUtil {개인 최종 정적 단순화성 sdfyhm = new SimpledateFormat ( "yyyymmdd"); public synchronized 정적 날짜 parseymdhms (문자열 소스) {try {return sdfyhm.parse (source); } catch (parseException e) {e.printstacktrace (); 새 날짜를 반환합니다 (); }}} 먼저 분석하겠습니다.
이 시점에서 parseymdhms () 함수는 동기화 된 수정을 사용하므로 작동이 스레드-미사일이므로 동기화해야합니다. 스레드 -unsafe는 단순한 simpledateformat의 구문 분석 방법 일 수 있습니다. 소스 코드를 확인하십시오. SimpledateFormat에는 전역 변수가 있습니다.
보호 된 캘린더 캘린더; 날짜 구문 분석 () {calendar.clear (); ... // 캘린더 및 기타 달력 날짜를 설정하기 위해 일부 작업을 수행합니다. gettime (); // 달력 시간 받기}Clear () 작업은 스레드가 불안해합니다.
또한 동기화 된 키워드를 사용하면 성능에 큰 영향을 미칩니다. 특히 멀티 스레딩을 할 때마다 ParseyMDHMS 메소드가 호출 될 때마다 동기화 판단이 이루어지고 동기화 자체가 매우 비싸므로 불합리한 솔루션입니다.
개선 방법
스레드 불안한 것은 여러 스레드가 공유 변수를 사용하여 발생하므로 여기서 우리는 ThreadLocal <MimpleDateFormat>을 사용하여 각 스레드에 대한 사본 변수를 별도로 생성합니다. 먼저 코드를 제공 한 다음이 문제의 원인을 분석하여 문제를 해결하십시오.
/** * Date Tool Class (ThreadLocal은 SimpledateFormat을 얻는 데 사용되며 다른 방법은 공통 규모에 의해 직접 복사 될 수 있습니다) * @Author Niu li * @date 2016/11/19 */public class datatuil {private static map <string, struledlocal <sdfmap = new Hashmap <stridlocal, smitlocal, smitlocal. 개인 정적 로거 로거 = loggerfactory.getLogger (dateUtil.class); 공개 최종 정적 문자열 mdhmss = "mmddhhmmssss"; 공개 최종 정적 문자열 ymdhms = "yyymmddhhmmss"; 공개 최종 정적 문자열 YMDHMS_ = "YYYY-MM-DD HH : MM : SS"; 공개 최종 정적 문자열 ymd = "yyyymmdd"; 공개 최종 정적 문자열 ymd_ = "yyyy-mm-dd"; 공개 최종 정적 문자열 hms = "hhmmss"; / ***지도의 키를 기반으로 해당 스레드의 SDF 인스턴스를 가져옵니다.* @Param 패턴 키 @ReTurn이 인스턴스*/ private static simpledateformat getSdf (최종 문자열 패턴) {ThreadLocal <MimpleDateFormat> sdfThread = sdfmap.get (패턴); if (sdfthread == null) {// sdfmap이 값을 여러 번 넣는 것을 방지하기 위해 이중 점검을하고, 더블 잠금 싱글 톤의 이유는 동일하게 동기화 된 (dateutil.class) {sdfthread = sdfmap.get (패턴); if (sdfThread == null) {logger.debug ( "새로운 패턴" + Pattern + "Map"); sdfthread = new ThreadLocal <MimpleDateFormat> () {@OverRide Protected SimpleDateFormat initialValue () {logger.debug ( "스레드 :" + 스레드. 새로운 simpledateformat (패턴)을 반환합니다. }}; sdfmap.put (패턴, sdfthread); }}} return sdfthread.get (); } / *** 지정된 패턴에 따라 날짜를 구문 분석* @param 날짜 구문 분석 날짜* @param 날짜 패턴 형식을 지정* @return 구문 분석 날짜 인스턴스* / public static date parsedate (문자열 날짜, 문자열 패턴) {if (date == nUR) {throw new new OppalarGumentcection ( "날짜가 없어야합니다"); } try {return getSdf (Pattern) .parse (날짜); } catch (parseException e) {e.printstacktrace (); logger.error ( "구문 분석 형식은 지원하지 않습니다 :"+pattern); } return null; } / *** 지정된 패턴에 따른 형식 날짜* @param 날짜 형식으로 표시되는 날짜* @param 패턴 형식* @return 구문 분석 형식* / public static string formatdate (날짜 날짜, 문자열 패턴) {if (date == null) {New New ImperalArgumentException ( "날짜가 없어야한다"); } else {return getSdf (Pattern) .format (날짜); }}}시험
동일한 패턴을 사용하여 메인 스레드와 다른 두 개를 자식 스레드에서 실행하십시오.
public static void main (String [] args) {dateUtil.formatdate (new date (), mdhmss); 새 스레드 (()-> {dateUtil.formatdate (new date (), mdhmss);}); start (); 새 스레드 (()-> {dateUtil.formatdate (new date (), mdhmss);}); start (); }로그 분석
put new sdf of pattern MMddHHmmssSSS to mapthread: Thread[main,5,main] init pattern: MMddHHmmssSSSthread: Thread[Thread-0,5,main] init pattern: MMddHHmmssSSSthread: Thread[Thread-1,5,main] init pattern: MMddHHmmssSSSS
분석
SDFMAP PUT이 한 번 입력 한 반면 SimpledateFormat은 코드에 3 개의 스레드가 있었기 때문에 3 번의 새로운 차례였습니다. 그렇다면 왜 이거야?
각 스레드 스레드의 경우 ThreadLocal.threadLocalMap ThreadLocals에 대한 전역 변수 참조가 있습니다. ThreadLocal.threadlocalmap이 threadLocal과 해당 값을 보유하는 threadLocal.ThreadLocalMap이 있습니다. 한 사진은 천 단어보다 낫습니다. 구조 다이어그램은 다음과 같습니다.
따라서 SDFMAP의 경우 구조 다이어그램이 변경됩니다
1. DateUtil.formatDate (새 날짜 (), MDHMSS)를 실행합니다.
// DateUtil.FormatDate (new Date (), MDHMSS) 분석을 처음 실행했을 때 개인 정적 SMIPLEDATEFORMAT getSdf (최종 문자열 패턴) {ThreadLocal <MimpleDateFormat> SDFTHREAD = SDFMAP.GET (패턴); // 획득 된 sdfThread는 null입니다. if (sdfthread == null) {synchronized (dateUtil.class) {sdfthread = sdfmap.get (Pattern); // SDFTHREAD는 여전히 NULL입니다. if (sdfThread == null) {// print logger.debug ( "Patter" + Pattern + "의 새 SDF를 맵핑") if 문을 입력하십시오. // ThreadLocal 인스턴스 생성 및 InitialValue 메소드를 재정의 = 새 스레드 로컬 <MimpleDateFormat> () {@Override Protected SimpleDateFormat initialValue () {logger.debug ( "스레드 :" + 스레드 .CurrentThread () + "Init Pattern); 새로운 simpledateformat (패턴)을 반환합니다. }}; // 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 ( "확인되지 않은") t result = (t) e.value; 반환 결과; }} return setInitialValue (); }즉, 값이 존재하지 않으면 setInitialValue () 메소드가 호출되며, 이는 InitialValue () 메소드를 호출하여 재정의하는 메소드입니다.
해당 로그 인쇄.
MMMDDHHMMSSSSS의 새로운 SDF를 MAPTHREAD : 스레드 [Main, 5, Main] Init Pattern : MMDDHHMMSSSSS에 넣으십시오.
2. 두 번째로 Child Thread에서 dateUtil.formatdate (new date (), mdhmss)를 실행하십시오.
//`dateUtil.formatDate (new date (), mdhmss);`개인 정적 smepledateformat getSdf (최종 문자열 패턴) {threadLocal <MimpleDateFormat> sdfththread = sdfmap.get (패턴); // 여기에서 얻은 sdfththread는 null이 아닙니다. block if if if (sdfthread == null) {synchronized (dateUtil.class) {sdfthread = sdfmap.get (패턴); if (sdfThread == null) {logger.debug ( "새로운 패턴" + Pattern + "Map"); sdfthread = new ThreadLocal <MimpleDateFormat> () {@OverRide Protected SimpleDateFormat initialValue () {logger.debug ( "스레드 :" + 스레드. 새로운 simpledateformat (패턴)을 반환합니다. }}; sdfmap.put (패턴, sdfthread); }}} // sdfthread.get ()을 직접 rolding으로 반환하려면 sdfthread.get (); }분석 sdfthread.get ()
//`dateUtil.formatdate (new date (), mdhmss);`public t get () {Thread T = Thread.CurrentThread (); // 현재 Child Thread Restle ThreadLocalMap Map = GetMap (t); // 자식 스레드에서 얻은 맵은 null입니다. if if if (map! = null) {threadLocalMap.entry e = map.getEntry (this); if (e! = null) {@suppresswarnings ( "확인되지 않은") t result = (t) e.value; 반환 결과; }} // 초기화를 직접 실행합니다. }해당 로그 :
스레드 [Thread-1,5, Main] init 패턴 : MMDDHHMMSSSSS
요약
어떤 시나리오에서 ThreadLocal을 사용하는 데 더 적합합니까? 누군가 StackoverFlow에 대해 꽤 좋은 답변을했습니다.
스레드 락 변수는 언제 그리고 어떻게 사용해야합니까?
가능한 (일반적인) 사용은 스레드 안전이 아닌 객체가있을 때이지만 해당 객체에 대한 액세스를 동기화하지 않으려는 것입니다 (SimpledateFormat을보고 있습니다). 대신, 각 스레드에 자체 인스턴스를 객체의 인스턴스를 제공하십시오.
참조 코드 :
Javaweb의 https://github.com/nl101531/util-demo
참조 :
이해하기 쉬운 방식으로 Java Threadlocal을 배우십시오
SimpledateFormat의 스레드 안전 문제 및 솔루션
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.