ฉันควรใช้ThreadLocal
ตัวแปรเมื่อใด
มันใช้งานอย่างไร?
RequestUtils.getCurrentRequestThreadLocal()
เช่น แม้ว่าจะไม่ได้กล่าวถึงสิ่งนี้ แต่ก็มีสาเหตุมาจากความจริงที่ว่า ThreadLocal นั้นไม่ได้สวยงามมากในกรณีส่วนใหญ่
ฉันควรใช้ThreadLocal
ตัวแปรเมื่อใด
มันใช้งานอย่างไร?
RequestUtils.getCurrentRequestThreadLocal()
เช่น แม้ว่าจะไม่ได้กล่าวถึงสิ่งนี้ แต่ก็มีสาเหตุมาจากความจริงที่ว่า ThreadLocal นั้นไม่ได้สวยงามมากในกรณีส่วนใหญ่
คำตอบ:
หนึ่งในการใช้งานที่เป็นไปได้ (และทั่วไป) คือเมื่อคุณมีวัตถุบางอย่างที่ไม่ปลอดภัยสำหรับเธรด แต่คุณต้องการหลีกเลี่ยงการซิงโครไนซ์การเข้าถึงวัตถุนั้น (ฉันกำลังดูคุณSimpleDateFormat ) ให้แต่ละอินสแตนซ์ของวัตถุ
ตัวอย่างเช่น:
public class Foo
{
// SimpleDateFormat is not thread-safe, so give one to each thread
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue()
{
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
public String formatIt(Date date)
{
return formatter.get().format(date);
}
}
เอกสารประกอบ
SimpleDateFormat
ค บางทีมันอาจจะดีกว่าที่จะใช้เป็นทางเลือกด้ายปลอดภัย หากคุณยอมรับว่าซิงเกิลตันนั้นThreadLocal
แย่ยิ่งกว่านั้นอีก
เนื่องจาก a ThreadLocal
เป็นการอ้างอิงถึงข้อมูลภายในชุดข้อมูลThread
คุณสามารถจบด้วยการรั่วไหลของการโหลดคลาสเมื่อใช้ThreadLocal
s ในแอ็พพลิเคชันเซิร์ฟเวอร์โดยใช้เธรดพูล คุณจะต้องระมัดระวังเกี่ยวกับการทำความสะอาดใด ๆThreadLocal
ของคุณget()
หรือset()
โดยใช้ThreadLocal
's remove()
วิธี
หากคุณไม่ได้ทำความสะอาดเมื่อเสร็จสิ้นการอ้างอิงใด ๆ ที่เก็บไว้ในคลาสที่โหลดเนื่องจากส่วนหนึ่งของ webapp ที่ใช้งานจะยังคงอยู่ในฮีปถาวรและจะไม่ได้รับการรวบรวมขยะ การปรับใช้ / ยกเลิกการปรับใช้ webapp จะไม่ล้างข้อมูลThread
อ้างอิงของคลาส webapp ของคุณเนื่องจากThread
ไม่ได้เป็นของ webapp ของคุณ การปรับใช้อย่างต่อเนื่องแต่ละครั้งจะสร้างอินสแตนซ์ใหม่ของคลาสซึ่งจะไม่ถูกรวบรวมขยะ
คุณจะจบลงด้วยข้อยกเว้นหน่วยความจำไม่เพียงพอเนื่องจากjava.lang.OutOfMemoryError: PermGen space
และหลังจาก googling บางอย่างอาจจะเพิ่มขึ้น-XX:MaxPermSize
แทนที่จะแก้ไขข้อผิดพลาด
หากคุณไม่สิ้นสุดประสบปัญหาเหล่านี้คุณสามารถกำหนดหัวข้อและชั้นจะรักษาอ้างอิงเหล่านี้โดยใช้การวิเคราะห์หน่วยความจำของ Eclipseและ / หรือโดยทำตามคำแนะนำของแฟรงก์ Kieviet ของและการติดตามผล
อัปเดต: ค้นพบบล็อกของ Alex Vasseur อีกครั้งซึ่งช่วยให้ฉันติดตามThreadLocal
ปัญหาบางอย่างที่ฉันมี
เฟรมเวิร์กจำนวนมากใช้ ThreadLocals เพื่อรักษาบริบทบางอย่างที่เกี่ยวข้องกับเธรดปัจจุบัน ตัวอย่างเช่นเมื่อธุรกรรมปัจจุบันถูกเก็บไว้ใน ThreadLocal คุณไม่จำเป็นต้องผ่านมันเป็นพารามิเตอร์ผ่านการเรียกใช้เมธอดทุกครั้งในกรณีที่บางคนลงสแต็กจำเป็นต้องเข้าถึง เว็บแอปพลิเคชันอาจเก็บข้อมูลเกี่ยวกับคำขอและเซสชันปัจจุบันใน ThreadLocal เพื่อให้แอปพลิเคชันเข้าถึงได้ง่าย ด้วย Guice คุณสามารถใช้ ThreadLocals เมื่อใช้ขอบเขตที่กำหนดเองสำหรับวัตถุที่ถูกฉีด ( ขอบเขตเซิร์ฟเล็ตเริ่มต้นของ Guice ซึ่งส่วนใหญ่อาจใช้ด้วย)
ThreadLocals เป็นตัวแปรทั่วโลกประเภทหนึ่ง (แม้ว่าจะมีความชั่วร้ายน้อยลงเล็กน้อยเนื่องจากถูก จำกัด ไว้ที่หนึ่งเธรด) ดังนั้นคุณควรใช้ความระมัดระวังเมื่อใช้เพื่อหลีกเลี่ยงผลข้างเคียงและการรั่วไหลของหน่วยความจำ ออกแบบ APIs ของคุณเพื่อให้ค่า ThreadLocal จะถูกล้างโดยอัตโนมัติเมื่อไม่ต้องการใช้อีกต่อไปและการใช้ API ที่ไม่ถูกต้องจะเป็นไปไม่ได้ (เช่นนี้ ) ThreadLocals สามารถใช้เพื่อทำให้โค้ดสะอาดขึ้นและในบางกรณีที่หายากพวกเขาเป็นวิธีเดียวที่จะทำให้บางสิ่งบางอย่างทำงานได้ (โครงการปัจจุบันของฉันมีสองกรณีดังกล่าวพวกเขาได้รับการบันทึกไว้ที่นี่ภายใต้ "เขตข้อมูลคงที่
ใน Java ถ้าคุณมี datum ที่สามารถเปลี่ยนแปลงได้ต่อเธรดตัวเลือกของคุณจะต้องส่ง datum นั้นไปยังทุกวิธีที่ต้องการ (หรืออาจต้องการ) หรือเชื่อมโยง datum กับ thread การส่งผ่านข้อมูลรอบตัวทุกที่อาจใช้การได้หากวิธีการทั้งหมดของคุณต้องผ่านตัวแปร "บริบท" ทั่วไปแล้ว
หากไม่ใช่กรณีดังกล่าวคุณอาจไม่ต้องการให้ลายเซ็นต์วิธีการของคุณยุ่งเหยิงด้วยพารามิเตอร์เพิ่มเติม ในโลกที่ไม่ใช่เธรดคุณสามารถแก้ปัญหาด้วยการเทียบเท่า Java ของตัวแปรทั่วโลก ในคำที่เธรดเทียบเท่าของตัวแปรทั่วโลกเป็นตัวแปรเธรดท้องถิ่น
'datum' is the singular form and 'data' is the plural form.
มีตัวอย่างที่ดีมากในหนังสือคือJava Concurrency ในการปฏิบัติ โดยที่ผู้แต่ง ( Joshua Bloch ) อธิบายว่าการ จำกัด เธรดเป็นหนึ่งในวิธีที่ง่ายที่สุดในการบรรลุความปลอดภัยของเธรดและThreadLocalเป็นวิธีการที่เป็นทางการมากขึ้นในการคงการเก็บเธรดไว้ ในที่สุดเขาก็อธิบายว่าผู้คนสามารถใช้วิธีการที่ไม่เหมาะสมได้อย่างไรโดยใช้มันเป็นตัวแปรระดับโลก
ฉันได้คัดลอกข้อความจากหนังสือที่กล่าวถึงแล้ว แต่รหัส 3.10 ขาดหายไปเนื่องจากไม่สำคัญที่จะเข้าใจว่าควรใช้ ThreadLocal ใด
ตัวแปรเธรดโลคัลมักถูกใช้เพื่อป้องกันการแบ่งใช้ในการออกแบบโดยยึดตาม Singletons ที่ไม่แน่นอนหรือตัวแปรโกลบอล ตัวอย่างเช่นแอปพลิเคชันแบบเธรดเดียวอาจรักษาการเชื่อมต่อฐานข้อมูลส่วนกลางที่เริ่มต้นเมื่อเริ่มต้นเพื่อหลีกเลี่ยงการผ่านการเชื่อมต่อกับทุกวิธี เนื่องจากการเชื่อมต่อ JDBC อาจไม่ปลอดภัยสำหรับเธรดแอ็พพลิเคชันแบบมัลติเธรดที่ใช้การเชื่อมต่อแบบโกลบอลโดยไม่มีการประสานงานเพิ่มเติมจึงไม่ได้รับการตอบกลับอย่างปลอดภัยเช่นกัน โดยใช้ ThreadLocal เพื่อจัดเก็บการเชื่อมต่อ JDBC เช่นเดียวกับใน ConnectionHolder ในรายการ 3.10 แต่ละเธรดจะมีการเชื่อมต่อของตัวเอง
ThreadLocal ใช้กันอย่างแพร่หลายในการนำเฟรมเวิร์กแอปพลิเคชันไปใช้ ตัวอย่างเช่นคอนเทนเนอร์ J2EE เชื่อมโยงบริบทการทำธุรกรรมกับเธรดที่กำลังดำเนินการสำหรับช่วงเวลาของการโทร EJB สิ่งนี้ถูกนำไปใช้อย่างง่ายดายโดยใช้ Thread-Local แบบคงที่ซึ่งถือบริบทการทำธุรกรรม: เมื่อรหัสเฟรมเวิร์กต้องการตรวจสอบว่าธุรกรรมใดที่กำลังทำงานอยู่จะดึงบริบทของธุรกรรมจาก ThreadLocal นี้ สิ่งนี้สะดวกในการลดความจำเป็นในการส่งผ่านข้อมูลบริบทการดำเนินการไปยังทุกวิธี แต่รับรหัสใด ๆ ที่ใช้กลไกนี้ไปยังกรอบงาน
เป็นการง่ายที่จะละเมิด ThreadLocal โดยการใช้คุณสมบัติการเก็บรักษาเธรดในฐานะสิทธิ์การใช้งานเพื่อใช้ตัวแปรทั่วโลกหรือเป็นวิธีการสร้างอาร์กิวเมนต์เมธอด“ ซ่อน” เช่นเดียวกับตัวแปรทั่วโลกตัวแปรของเธรดโลคัลสามารถเบี่ยงเบนจากการนำมาใช้ซ้ำและแนะนำข้อต่อที่ซ่อนอยู่ในคลาสดังนั้นจึงควรใช้ด้วยความระมัดระวัง
โดยพื้นฐานแล้วเมื่อคุณต้องการค่าของตัวแปรขึ้นอยู่กับเธรดปัจจุบันและไม่สะดวกที่คุณจะแนบค่ากับเธรดในวิธีอื่น (ตัวอย่างเช่นเธรดคลาสย่อย)
กรณีทั่วไปคือที่เฟรมเวิร์กอื่นสร้างเธรดที่โค้ดของคุณกำลังทำงานอยู่เช่นคอนเทนเนอร์ servlet หรือที่มันเหมาะสมกว่าที่จะใช้ ThreadLocal เนื่องจากตัวแปรของคุณนั้น "อยู่ในตำแหน่งตรรกะ" (แทนที่จะเป็นตัวแปร ห้อยจากคลาสย่อยของเธรดหรือในแผนที่แฮชอื่น ๆ )
บนเว็บไซต์ของฉันฉันมีการสนทนาและตัวอย่างเพิ่มเติมเกี่ยวกับการใช้ ThreadLocalที่อาจเป็นที่สนใจ
บางคนสนับสนุนการใช้ ThreadLocal เป็นวิธีการแนบ "thread ID" กับแต่ละเธรดในอัลกอริทึมที่เกิดขึ้นพร้อมกันที่คุณต้องการหมายเลขเธรด (ดูเช่น Herlihy & Shavit) ในกรณีเช่นนี้ให้ตรวจสอบว่าคุณได้รับประโยชน์จริงๆ!
ThreadLocal ใน Java ได้รับการแนะนำให้รู้จักกับ JDK 1.2 แต่ต่อมาถูกสร้างใน JDK 1.5 เพื่อแนะนำความปลอดภัยของประเภทบนตัวแปรของ ThreadLocal
ThreadLocal สามารถเชื่อมโยงกับขอบเขตของเธรดรหัสทั้งหมดที่ดำเนินการโดยเธรดมีการเข้าถึงตัวแปร ThreadLocal แต่สองเธรดไม่สามารถมองเห็นตัวแปร ThreadLocal ซึ่งกันและกันได้
แต่ละเธรดมีสำเนาพิเศษของตัวแปร ThreadLocal ซึ่งมีสิทธิ์ในการรวบรวมขยะหลังจากที่เธรดเสร็จสิ้นหรือเสียชีวิตตามปกติหรือเนื่องมาจากการยกเว้นใด ๆ เนื่องจากตัวแปร ThreadLocal เหล่านั้นไม่มีการอ้างอิงสดอื่น ๆ
ตัวแปร ThreadLocal ใน Java โดยทั่วไปเป็นเขตข้อมูลคงที่ส่วนตัวในชั้นเรียนและรักษาสถานะของมันในหัวข้อ
อ่านเพิ่มเติม: ThreadLocal ใน Java - ตัวอย่างโปรแกรมและบทช่วยสอน
เอกสารบอกว่ามันดีมาก: "แต่ละเธรดที่เข้าถึง [ตัวแปรเธรดโลคัล] (ผ่านเมธอด get หรือ set) มีสำเนาของตัวแปรที่เริ่มต้นได้อย่างอิสระ"
คุณใช้หนึ่งเธรดเมื่อแต่ละเธรดต้องมีสำเนาของตัวเอง โดยค่าเริ่มต้นข้อมูลจะถูกแชร์ระหว่างเธรด
เซิร์ฟเวอร์ Webapp อาจเก็บเธรดพูลไว้และThreadLocal
ควรลบ var ออกก่อนตอบกลับไปยังไคลเอ็นต์ดังนั้นเธรดปัจจุบันอาจถูกนำมาใช้ใหม่โดยการร้องขอครั้งต่อไป
กรณีการใช้งานสองกรณีที่สามารถใช้ตัวแปร threadlocal -
1 - เมื่อเรามีข้อกำหนดที่จะเชื่อมโยงสถานะกับเธรด (เช่น ID ผู้ใช้หรือ ID ธุรกรรม) ซึ่งมักจะเกิดขึ้นกับเว็บแอ็พพลิเคชันที่ทุกครั้งที่ร้องขอไปยังเซิร์ฟเล็ตมี transactionID ที่ไม่ซ้ำกัน
// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
โปรดทราบว่าที่นี่มีการใช้วิธีการเริ่มต้นโดยใช้การแสดงออกแลมบ์ดา
2- กรณีการใช้งานอื่นคือเมื่อเราต้องการให้มีอินสแตนซ์ของเธรดที่ปลอดภัยและเราไม่ต้องการใช้การซิงโครไนซ์เนื่องจากต้นทุนการทำงานที่มีการซิงโครไนซ์เพิ่มขึ้น กรณีหนึ่งคือเมื่อใช้ SimpleDateFormat เนื่องจาก SimpleDateFormat ไม่ปลอดภัยสำหรับเธรดดังนั้นเราจึงต้องจัดเตรียมกลไกเพื่อทำให้เธรดปลอดภัย
public class ThreadLocalDemo1 implements Runnable {
// threadlocal variable is created
private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue(){
System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
return new SimpleDateFormat("dd/MM/yyyy");
}
};
public static void main(String[] args) {
ThreadLocalDemo1 td = new ThreadLocalDemo1();
// Two threads are created
Thread t1 = new Thread(td, "Thread-1");
Thread t2 = new Thread(td, "Thread-2");
t1.start();
t2.start();
}
@Override
public void run() {
System.out.println("Thread run execution started for " + Thread.currentThread().getName());
System.out.println("Date formatter pattern is " + dateFormat.get().toPattern());
System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
}
}
ตั้งแต่ Java 8 รีลีสมีวิธีการประกาศเพิ่มเติมเพื่อเริ่มต้นThreadLocal
:
ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");
ก่อนที่จะปล่อย Java 8 คุณต้องทำสิ่งต่อไปนี้:
ThreadLocal<String> local = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "init value";
}
};
นอกจากนี้หากวิธีการสร้างอินสแตนซ์ (ตัวสร้างวิธีการจากโรงงาน) ของคลาสที่ใช้ThreadLocal
ไม่ได้ใช้พารามิเตอร์ใด ๆ คุณสามารถใช้การอ้างอิงวิธีการ (แนะนำใน Java 8):
class NotThreadSafe {
// no parameters
public NotThreadSafe(){}
}
ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);
หมายเหตุ: การ
ประเมินผลเป็นสันหลังยาวเนื่องจากคุณผ่านjava.util.function.Supplier
แลมบ์ดาที่ได้รับการประเมินก็ต่อเมื่อThreadLocal#get
ถูกเรียกเท่านั้น แต่ยังไม่ได้ประเมินค่าก่อนหน้านี้
คุณต้องระวังอย่างมากกับรูปแบบ ThreadLocal มีบางด้านที่สำคัญเช่นฟิลที่กล่าวถึง แต่สิ่งที่ไม่ได้กล่าวถึงคือการทำให้แน่ใจว่ารหัสที่ตั้งค่าบริบทของ ThreadLocal ไม่ใช่ "ผู้เข้าร่วมใหม่"
สิ่งที่ไม่ดีสามารถเกิดขึ้นได้เมื่อโค้ดที่ตั้งค่าข้อมูลถูกเรียกใช้เป็นครั้งที่สองหรือสามเนื่องจากข้อมูลในเธรดของคุณสามารถเริ่มการกลายพันธุ์เมื่อคุณไม่คาดหวัง ดังนั้นโปรดตรวจสอบให้แน่ใจว่าไม่ได้มีการตั้งค่าข้อมูล ThreadLocal ก่อนที่คุณจะตั้งค่าอีกครั้ง
ThreadLocal
รูปแบบ หากคุณทำF(){ member=random(); F2(); write(member); }
และ F2 แทนที่สมาชิกด้วยค่าใหม่เห็นได้ชัดว่าwrite(member)
จะไม่เขียนหมายเลขที่คุณได้random()
เอ็ด นี่เป็นเพียงสามัญสำนึก ในทำนองเดียวกันถ้าคุณทำเช่นF(){ F(); }
นั้นโชค gd กับวงวนไม่สิ้นสุดของคุณ! ThreadLocal
นี่คือความจริงทุกที่และไม่ได้เฉพาะการ
เมื่อไหร่?
เมื่อวัตถุไม่ปลอดภัยต่อเธรดแทนการซิงโครไนซ์ซึ่งขัดขวางความสามารถในการปรับขนาดได้ให้วัตถุหนึ่งเดียวกับทุกเธรดและเก็บขอบเขตของเธรดซึ่งเป็น ThreadLocal หนึ่งในวัตถุที่ใช้บ่อยที่สุด แต่ไม่ใช่วัตถุที่ปลอดภัยของเธรดคือการเชื่อมต่อฐานข้อมูลและ JMSConnection
ได้อย่างไร
ตัวอย่างหนึ่งคือ Spring Framework ใช้ ThreadLocal อย่างมากสำหรับการจัดการธุรกรรมที่อยู่เบื้องหลังโดยเก็บวัตถุการเชื่อมต่อเหล่านี้ไว้ในตัวแปร ThreadLocal ในระดับสูงเมื่อเริ่มทำธุรกรรมจะได้รับการเชื่อมต่อ (และปิดใช้งานการส่งอัตโนมัติ) และเก็บไว้ใน ThreadLocal บนการเรียก db เพิ่มเติมจะใช้การเชื่อมต่อเดียวกันเพื่อสื่อสารกับ db ในตอนท้ายจะใช้การเชื่อมต่อจาก ThreadLocal และกระทำ (หรือย้อนกลับ) การทำธุรกรรมและปล่อยการเชื่อมต่อ
ฉันคิดว่า log4j ยังใช้ ThreadLocal สำหรับการบำรุงรักษา MDC
ThreadLocal
มีประโยชน์เมื่อคุณต้องการมีสถานะบางอย่างที่ไม่ควรใช้ร่วมกันระหว่างเธรดที่แตกต่างกัน แต่ควรสามารถเข้าถึงได้จากแต่ละเธรดตลอดอายุการใช้งาน
ตัวอย่างเช่นลองนึกภาพเว็บแอปพลิเคชั่นโดยที่แต่ละคำร้องขอจะถูกให้บริการโดยเธรดที่แตกต่างกัน ลองนึกภาพว่าสำหรับคำขอแต่ละครั้งคุณจำเป็นต้องมีข้อมูลหลาย ๆ ครั้งซึ่งค่อนข้างแพงในการคำนวณ อย่างไรก็ตามข้อมูลนั้นอาจมีการเปลี่ยนแปลงสำหรับแต่ละคำขอที่เข้ามาซึ่งหมายความว่าคุณไม่สามารถใช้แคชธรรมดา ทางออกที่ง่ายและรวดเร็วสำหรับปัญหานี้คือการมีThreadLocal
ตัวแปรการเข้าถึงข้อมูลนี้เพื่อให้คุณต้องคำนวณเพียงครั้งเดียวสำหรับแต่ละคำขอ แน่นอนว่าปัญหานี้สามารถแก้ไขได้โดยไม่ต้องใช้ThreadLocal
แต่ฉันคิดขึ้นเพื่อประกอบการอธิบาย
ที่กล่าวไว้ในใจว่าThreadLocal
เป็นรูปแบบของรัฐทั่วโลก เป็นผลให้มันมีความหมายอื่น ๆ อีกมากมายและควรใช้เฉพาะหลังจากที่พิจารณาโซลูชั่นที่เป็นไปได้อื่น ๆ ทั้งหมด
ThreadLocal
เป็นข้อมูลวัตถุ ...
ThreadLocal
ซึ่งสามารถเข้าถึงได้ทั่วโลก อย่างที่คุณพูดมันยังสามารถใช้เป็นฟิลด์คลาสที่ จำกัด การมองเห็นได้
ไม่มีอะไรใหม่จริงๆที่นี่ แต่ฉันค้นพบในวันนี้ว่าThreadLocal
มีประโยชน์มากเมื่อใช้การตรวจสอบความถูกต้อง Bean ในเว็บแอปพลิเคชัน ข้อความการตรวจสอบจะมีการแปล Locale.getDefault()
แต่จากการใช้ค่าเริ่มต้น คุณสามารถกำหนดค่าValidator
กับการที่แตกต่างกันMessageInterpolator
แต่มีวิธีที่จะระบุไม่มีเมื่อคุณเรียกLocale
validate
ดังนั้นคุณสามารถสร้างแบบคงที่ThreadLocal<Locale>
(หรือดีกว่ายังภาชนะทั่วไปที่มีสิ่งอื่น ๆ ที่คุณอาจจะต้องมีThreadLocal
แล้วMessageInterpolator
เลือกกำหนดเองของคุณLocale
จากนั้นขั้นตอนต่อไปคือการเขียนServletFilter
ซึ่งใช้ค่าเซสชั่นหรือrequest.getLocale()
เลือกสถานที่และร้านค้า ในThreadLocal
การอ้างอิงของคุณ
ตามที่ได้รับการกล่าวถึงโดย @unknown (google) การใช้งานคือการกำหนดตัวแปรทั่วโลกซึ่งค่าที่อ้างอิงสามารถไม่ซ้ำกันในแต่ละเธรด โดยทั่วไปแล้วการใช้งานจะเกี่ยวข้องกับการจัดเก็บข้อมูลบริบทบางอย่างที่เชื่อมโยงกับเธรดการดำเนินการปัจจุบัน
เราใช้ในสภาพแวดล้อม Java EE เพื่อส่งข้อมูลประจำตัวของผู้ใช้ไปยังคลาสที่ไม่ได้รับรู้ Java EE (ไม่มีสิทธิ์เข้าถึง HttpSession หรือ EJB SessionContext) วิธีนี้รหัสซึ่งทำให้การใช้งานของตัวตนสำหรับการดำเนินงานตามความปลอดภัยสามารถเข้าถึงตัวตนได้จากทุกที่โดยไม่ต้องผ่านมันอย่างชัดเจนในทุกวิธีการโทร
รอบการร้องขอ / ตอบสนองของการดำเนินการในการโทร Java EE ส่วนใหญ่ทำให้การใช้งานประเภทนี้ง่ายเนื่องจากให้จุดเข้าและออกที่กำหนดไว้อย่างดีเพื่อตั้งค่าและยกเลิกการตั้งค่า ThreadLocal
ThreadLocal จะตรวจสอบให้แน่ใจว่าการเข้าถึงออบเจ็กต์ที่ไม่แน่นอนโดยหลายเธรดในวิธีการซิงโครไนซ์ที่ไม่ซิงโครไนซ์หมายถึงการทำให้ออบเจ็กต์ที่เปลี่ยนแปลงไม่ได้
นี่คือความสำเร็จโดยการให้อินสแตนซ์ใหม่ของวัตถุที่ไม่แน่นอนสำหรับแต่ละเธรดลองเข้าถึง ดังนั้นจึงเป็นสำเนาโลคัลไปยังแต่ละเธรด นี่คือการแฮ็กที่ทำให้ตัวแปรอินสแตนซ์ในวิธีการเข้าถึงเหมือนตัวแปรโลคอล เมื่อคุณทราบถึงวิธีการที่ตัวแปรโลคอลใช้งานได้กับเธรดเท่านั้นความแตกต่างอย่างหนึ่งคือ ตัวแปรโลคอลเมธอดจะไม่พร้อมใช้งานกับเธรดเมื่อการเรียกใช้เมธอดสิ้นสุดลงเนื่องจากวัตถุที่ไม่แน่นอนซึ่งแบ่งใช้กับ threadlocal จะสามารถใช้ได้ในหลายวิธีจนกว่าเราจะล้างข้อมูล
โดยคำจำกัดความ:
คลาส ThreadLocal ใน Java ช่วยให้คุณสร้างตัวแปรที่สามารถอ่านและเขียนโดยเธรดเดียวกันเท่านั้น ดังนั้นแม้ว่าเธรดสองตัวกำลังเรียกใช้รหัสเดียวกันและรหัสนั้นมีการอ้างอิงถึงตัวแปร ThreadLocal ดังนั้นทั้งสองเธรดจะไม่สามารถเห็นตัวแปร ThreadLocal ของกันและกันได้
แต่ละThread
ใน java มีThreadLocalMap
อยู่ในนั้น
ที่ไหน
Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.
บรรลุ ThreadLocal:
ตอนนี้สร้างคลาส wrapper สำหรับ ThreadLocal ซึ่งจะถือวัตถุที่ไม่แน่นอนเช่นด้านล่าง (มีหรือไม่มีinitialValue()
)
ตอนนี้ getter และ setter ของ wrapper นี้จะทำงานบนอินสแตนซ์ของ threadlocal แทนที่จะเป็นอ็อบเจกต์ที่ไม่แน่นอน
หาก getter () ของ threadlocal ไม่พบค่าใด ๆ ใน threadlocalmap ของThread
; จากนั้นมันจะเรียกใช้ initialValue () เพื่อรับสำเนาส่วนตัวที่เกี่ยวกับเธรด
class SimpleDateFormatInstancePerThread {
private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") {
UUID id = UUID.randomUUID();
@Override
public String toString() {
return id.toString();
};
};
System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName());
return dateFormat;
}
};
/*
* Every time there is a call for DateFormat, ThreadLocal will return calling
* Thread's copy of SimpleDateFormat
*/
public static DateFormat getDateFormatter() {
return dateFormatHolder.get();
}
public static void cleanup() {
dateFormatHolder.remove();
}
}
ตอนนี้wrapper.getDateFormatter()
จะเรียกthreadlocal.get()
และที่จะตรวจสอบการcurrentThread.threadLocalMap
มีนี้ (ThreadLocal) ตัวอย่างเช่น
ถ้าใช่คืนค่า (SimpleDateFormat) สำหรับอินสแตนซ์ threadlocal ที่สอดคล้องกัน
เพิ่มแผนที่ด้วยอินสแตนซ์ของ threadlocal นี้ initialValue ()
พร้อมกับความปลอดภัยของด้ายในชั้นที่ไม่แน่นอนนี้ โดยแต่ละเธรดกำลังทำงานกับอินสแตนซ์ที่เปลี่ยนแปลงได้ของตัวเอง แต่มีอินสแตนซ์ของเธรดเธรดเดียวกัน หมายถึงเธรดทั้งหมดจะใช้อินสแตนซ์ของ ThreadLocal เดียวกันกับคีย์ แต่อินสแตนซ์ SimpleDateFormat ที่แตกต่างกันเป็นค่า
https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java
ตัวแปรเธรดโลคัลมักถูกใช้เพื่อป้องกันการแบ่งใช้ในการออกแบบโดยยึดตาม Singletons ที่ไม่แน่นอนหรือตัวแปรโกลบอล
มันสามารถใช้ในสถานการณ์เช่นการแยกการเชื่อมต่อ JDBC สำหรับแต่ละหัวข้อเมื่อคุณไม่ได้ใช้กลุ่มการเชื่อมต่อ
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
เมื่อคุณเรียกใช้ getConnection มันจะส่งคืนการเชื่อมต่อที่เกี่ยวข้องกับเธรดนั้นสามารถทำได้เช่นเดียวกันกับคุณสมบัติอื่น ๆ เช่น dateformat บริบทการทำธุรกรรมที่คุณไม่ต้องการแชร์ระหว่างเธรด
คุณสามารถใช้ตัวแปรท้องถิ่นสำหรับสิ่งเดียวกันได้เช่นกัน แต่ทรัพยากรเหล่านี้มักใช้เวลาในการสร้างดังนั้นคุณจึงไม่ต้องการสร้างมันซ้ำแล้วซ้ำอีกเมื่อใดก็ตามที่คุณทำตรรกะทางธุรกิจกับพวกเขา อย่างไรก็ตามค่า ThreadLocal จะถูกเก็บไว้ในวัตถุของเธรดและทันทีที่เธรดถูกรวบรวมขยะค่าเหล่านี้ก็จะหายไปด้วย
ลิงค์นี้อธิบายการใช้งาน ThreadLocal ได้เป็นอย่างดี
ThreadLocalชั้นใน Java ช่วยให้คุณสร้างตัวแปรที่สามารถอ่านและเขียนโดยหัวข้อเดียวกัน ดังนั้นแม้ว่าเธรดสองตัวกำลังเรียกใช้งานรหัสเดียวกันและรหัสนั้นมีการอ้างอิงถึงตัวแปร ThreadLocal ดังนั้นทั้งสองเธรดจะไม่สามารถเห็นตัวแปร ThreadLocal ของกันและกันได้
มี 3 สถานการณ์สำหรับการใช้ตัวช่วยคลาสเช่น SimpleDateFormat ในรหัสแบบหลายเธรดซึ่งสิ่งที่ดีที่สุดคือใช้ThreadLocal
สถานการณ์
1-การใช้Like share objectโดยใช้กลไกการล็อคหรือการซิงโครไนซ์ซึ่งทำให้แอปช้าลง
2-การใช้เป็นวัตถุท้องถิ่นภายในวิธีการ
ในสถานการณ์สมมตินี้ถ้าเรามี4 เธรดแต่ละคนเรียกเมธอด 1000ครั้งเราจะสร้างวัตถุ SimpleDateFormat
4000 รายการและรอให้ GC ลบทิ้ง
3-การใช้ThreadLocal
ถ้าเรามี 4 ด้ายและเราให้กับแต่ละกรณี SimpleDateFormat ด้ายหนึ่ง
เพื่อให้เรามี4 กระทู้ , 4 วัตถุของ SimpleDateFormat
ไม่จำเป็นต้องใช้กลไกการล็อคและการสร้างและทำลายวัตถุ (ความซับซ้อนของเวลาที่ดีและความซับซ้อนของพื้นที่)
[สำหรับการอ้างอิง] ThreadLocal ไม่สามารถแก้ปัญหาการอัพเดทของวัตถุที่ใช้ร่วมกันได้ ขอแนะนำให้ใช้วัตถุ staticThreadLocal ซึ่งใช้ร่วมกันโดยการดำเนินการทั้งหมดในชุดข้อความเดียวกัน [บังคับ] ลบ () วิธีการจะต้องดำเนินการโดยตัวแปร ThreadLocal โดยเฉพาะอย่างยิ่งเมื่อใช้กลุ่มสระว่ายน้ำที่กระทู้มักจะถูกนำมาใช้ซ้ำ มิฉะนั้นอาจส่งผลกระทบต่อตรรกะทางธุรกิจที่ตามมาและทำให้เกิดปัญหาที่ไม่คาดคิดเช่นการรั่วไหลของหน่วยความจำ
การแคชบางครั้งคุณต้องคำนวณค่าจำนวนมากเวลาเดียวกันโดยการเก็บชุดของอินพุตสุดท้ายไปยังวิธีการและผลลัพธ์ที่คุณสามารถเพิ่มความเร็วรหัส ด้วยการใช้ Thread Local Storage คุณไม่ต้องคิดถึงการล็อค
ThreadLocal เป็นฟังก์ชันที่จัดเตรียมเป็นพิเศษโดย JVM เพื่อให้มีพื้นที่เก็บข้อมูลแยกต่างหากสำหรับเธรดเท่านั้น ชอบค่าของตัวแปรที่กำหนดขอบเขตอินสแตนซ์จะถูกผูกไว้กับอินสแตนซ์ที่กำหนดของชั้นเรียนเท่านั้น แต่ละวัตถุมีค่าเพียงอย่างเดียวและพวกเขาไม่สามารถมองเห็นค่าอื่น ๆ ดังนั้นเป็นแนวคิดของตัวแปร ThreadLocal พวกเขาอยู่ในท้องถิ่นเพื่อด้ายในความรู้สึกของวัตถุตัวอย่างด้ายอื่น ๆ ยกเว้นที่สร้างมันไม่สามารถมองเห็นได้ ดูที่นี่
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
public static void main(String[] args) {
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
}
}
Threadlocal เป็นวิธีที่ง่ายมากในการทำให้วัตถุกลับมาใช้ใหม่ได้โดยไม่มีค่าใช้จ่าย
ฉันมีสถานการณ์ที่หลายเธรดกำลังสร้างภาพแคชที่ไม่แน่นอนในการแจ้งเตือนการอัปเดตแต่ละครั้ง
ฉันใช้ Threadlocal ในแต่ละเธรดแล้วแต่ละเธรดจะต้องรีเซ็ตภาพเก่าแล้วอัปเดตอีกครั้งจากแคชในการแจ้งเตือนการอัปเดตแต่ละครั้ง
วัตถุที่นำกลับมาใช้ใหม่ได้ตามปกติจากกลุ่มวัตถุมีค่าใช้จ่ายด้านความปลอดภัยของเธรดที่เกี่ยวข้องกับวัตถุเหล่านั้นในขณะที่วิธีนี้ไม่มีเลย
ลองตัวอย่างเล็ก ๆ นี้เพื่อให้เข้าใจถึงตัวแปรของ ThreadLocal:
public class Book implements Runnable {
private static final ThreadLocal<List<String>> WORDS = ThreadLocal.withInitial(ArrayList::new);
private final String bookName; // It is also the thread's name
private final List<String> words;
public Book(String bookName, List<String> words) {
this.bookName = bookName;
this.words = Collections.unmodifiableList(words);
}
public void run() {
WORDS.get().addAll(words);
System.out.printf("Result %s: '%s'.%n", bookName, String.join(", ", WORDS.get()));
}
public static void main(String[] args) {
Thread t1 = new Thread(new Book("BookA", Arrays.asList("wordA1", "wordA2", "wordA3")));
Thread t2 = new Thread(new Book("BookB", Arrays.asList("wordB1", "wordB2")));
t1.start();
t2.start();
}
}
เอาต์พุตคอนโซลหากเธรด BookA เสร็จสิ้นก่อน
ผลลัพธ์ผลลัพธ์ A: 'wordA1, wordA2, wordA3'
ผลการสืบค้น B: 'wordB1, wordB2'
เอาต์พุตคอนโซลหากเธรด BookB เสร็จสิ้นก่อน
ผลลัพธ์ BookB: 'wordB1, wordB2'
ผลการสืบค้น A: 'wordA1, wordA2, wordA3'