เหตุใดตัวแปร Java ThreadLocal จึงควรเป็นแบบคงที่


101

ฉันกำลังอ่าน JavaDoc สำหรับ Threadlocal ที่นี่

https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html

และมีข้อความระบุว่า "อินสแตนซ์ ThreadLocal มักเป็นฟิลด์สแตติกส่วนตัวในคลาสที่ต้องการเชื่อมโยงสถานะกับเธรด (เช่น ID ผู้ใช้หรือรหัสธุรกรรม)"

แต่คำถามของฉันคือทำไมพวกเขาถึงเลือกที่จะทำให้เป็นแบบคงที่ (โดยทั่วไป) - มันทำให้เกิดความสับสนเล็กน้อยที่จะมีสถานะ "ต่อเธรด" แต่ฟิลด์เป็นแบบคงที่?

คำตอบ:


131

เพราะถ้าเป็นฟิลด์ระดับอินสแตนซ์จริงๆแล้วมันจะเป็น "ต่อเธรด - ต่ออินสแตนซ์" ไม่ใช่แค่ "ต่อเธรด" ที่รับประกัน นั่นไม่ใช่ความหมายตามปกติที่คุณกำลังมองหา

โดยปกติแล้วจะมีบางอย่างเช่นวัตถุที่กำหนดขอบเขตไว้ที่การสนทนาของผู้ใช้คำขอทางเว็บ ฯลฯ คุณไม่ต้องการให้มีการกำหนดขอบเขตย่อยให้กับอินสแตนซ์ของคลาส
คำขอเว็บหนึ่งรายการ => หนึ่งเซสชันการคงอยู่
ไม่ใช่คำขอเว็บเดียว => หนึ่งเซสชันการคงอยู่ต่อออบเจ็กต์


2
ฉันชอบคำอธิบายนี้เพราะมันแสดงให้เห็นว่าจะใช้ ThreadLocal อย่างไร
kellyfj

4
ต่อเธรดต่ออินสแตนซ์อาจเป็นความหมายที่มีประโยชน์ แต่การใช้งานส่วนใหญ่สำหรับรูปแบบนั้นจะเกี่ยวข้องกับอ็อบเจ็กต์จำนวนมากดังนั้นจึงเป็นการดีกว่าที่จะใช้ a ThreadLocalเพื่อเก็บการอ้างอิงไปยังชุดแฮชที่แมปอ็อบเจ็กต์กับอินสแตนซ์ต่อเธรด
supercat

@optional มันแค่หมายความว่าแต่ละอินสแตนซ์ของ non-static ThreadLocalจะถือเป็นข้อมูล thread-local ของตัวเองแม้ว่าThreadLocalอินสแตนซ์เหล่านั้นจะอยู่ในเธรดเดียวกันก็ตาม ไม่จำเป็นต้องทำอย่างนั้น - ฉันคิดว่ามันอาจจะเป็นรูปแบบที่ได้รับความนิยมน้อยที่สุดของทั้งสอง
geg

17

ทำให้เป็นแบบคงที่หรือถ้าคุณพยายามหลีกเลี่ยงเขตข้อมูลคงที่ใด ๆ ในชั้นเรียนของคุณ - ทำให้ชั้นเรียนเป็นซิงเกิลตันจากนั้นคุณสามารถใช้ ThreadLocal ระดับอินสแตนซ์ได้อย่างปลอดภัยตราบเท่าที่คุณมีซิงเกิลตันนั้นพร้อมใช้งานทั่วโลก



3

เหตุผลก็คือตัวแปรถูกเข้าถึงผ่านตัวชี้ที่เกี่ยวข้องกับเธรด พวกมันทำหน้าที่เหมือนตัวแปรส่วนกลางที่มีขอบเขตเธรดดังนั้นค่าคงที่จึงเหมาะสมที่สุด นี่เป็นวิธีที่คุณจะได้เธรดสถานะท้องถิ่นในสิ่งต่างๆเช่น pthreads ดังนั้นนี่อาจเป็นอุบัติเหตุของประวัติศาสตร์และการนำไปใช้งาน


1

การใช้งานสำหรับ threadlocal บนอินสแตนซ์ต่อเธรดคือถ้าคุณต้องการให้บางสิ่งบางอย่างสามารถมองเห็นได้ในทุกวิธีของอ็อบเจ็กต์และทำให้เธรดปลอดภัยโดยไม่ต้องซิงโครไนซ์การเข้าถึงเหมือนกับที่คุณทำในฟิลด์ธรรมดา


1

อ้างถึงสิ่งนี้ทำให้เกิดความเข้าใจที่ดีขึ้น

ในระยะสั้นThreadLocalออบเจ็กต์ทำงานเหมือนแมปคีย์ - ค่า เมื่อเธรดเรียกใช้ThreadLocal get/setเมธอดมันจะดึง / จัดเก็บอ็อบเจ็กต์เธรดในคีย์ของแผนที่และค่าในค่าของแผนที่ นั่นเป็นเหตุผลที่เธรดที่แตกต่างกันจึงมีการคัดลอกค่าที่แตกต่างกัน (ซึ่งคุณต้องการจัดเก็บไว้ในเครื่อง) เนื่องจากมันอยู่ในรายการของแผนที่ที่แตกต่างกัน

นั่นเป็นเหตุผลที่คุณต้องการเพียงแผนที่เดียวเพื่อเก็บค่าทั้งหมดไว้ แม้ว่าจะไม่จำเป็น แต่คุณสามารถมีหลายแผนที่ (โดยไม่ต้องประกาศแบบคงที่) เพื่อเก็บอ็อบเจ็กต์เธรดแต่ละอันได้เช่นกันซึ่งมันซ้ำซ้อนโดยสิ้นเชิงนั่นเป็นเหตุผลที่ต้องการตัวแปรคงที่


-1

static final ThreadLocal ตัวแปรคือเธรดปลอดภัย

staticทำให้ตัวแปร ThreadLocal พร้อมใช้งานในหลายคลาสสำหรับเธรดที่เกี่ยวข้องเท่านั้น เป็นการแยกตัวแปร Global ชนิดหนึ่งของตัวแปรโลคัลเธรดที่เกี่ยวข้องในหลายคลาส

เราสามารถตรวจสอบความปลอดภัยของเธรดนี้ด้วยตัวอย่างโค้ดต่อไปนี้

  • CurrentUser - เก็บ ID ผู้ใช้ปัจจุบันใน ThreadLocal
  • TestService- บริการง่ายๆด้วยวิธีการ - getUser()เพื่อดึงผู้ใช้ปัจจุบันจาก CurrentUser
  • TestThread - คลาสนี้ใช้สำหรับสร้างเธรดหลายเธรดและตั้งค่าผู้ใช้พร้อมกัน

.

public class CurrentUser

public class CurrentUser {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<String>();

public static ThreadLocal<String> getCurrent() {
    return CURRENT;
}

public static void setCurrent(String user) {
    CURRENT.set(user);
}

}

public class TestService {

public String getUser() {
    return CurrentUser.getCurrent().get();
}

}

.

import java.util.ArrayList;
import java.util.List;

public class TestThread {

public static void main(String[] args) {

  List<Integer> integerList = new ArrayList<>();

  //creates a List of 100 integers
  for (int i = 0; i < 100; i++) {

    integerList.add(i);
  }

  //parallel stream to test concurrent thread execution
  integerList.parallelStream().forEach(intValue -> {

    //All concurrent thread will set the user as "intValue"
    CurrentUser.setCurrent("" + intValue);
    //Thread creates a sample instance for TestService class
    TestService testService = new TestService();
    //Print the respective thread name along with "intValue" value and current user. 
    System.out.println("Start-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());

    try {
      //all concurrent thread will wait for 3 seconds
      Thread.sleep(3000l);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    //Print the respective thread name along with "intValue" value and current user.
    System.out.println("End-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());
  });

}

}

.

เรียกใช้คลาสหลักของ TestThread เอาต์พุต -

Start-main->62->62
Start-ForkJoinPool.commonPool-worker-2->31->31
Start-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-1->87->87
End-main->62->62
End-ForkJoinPool.commonPool-worker-1->87->87
End-ForkJoinPool.commonPool-worker-2->31->31
End-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-2->32->32
Start-ForkJoinPool.commonPool-worker-3->82->82
Start-ForkJoinPool.commonPool-worker-1->88->88
Start-main->63->63
End-ForkJoinPool.commonPool-worker-1->88->88
End-main->63->63
...

สรุปการวิเคราะห์

  1. เธรด "main" เริ่มต้นและตั้งค่าผู้ใช้ปัจจุบันเป็น "62" แบบขนาน "ForkJoinPool.commonPool-worker-2" เธรดเริ่มต้นและตั้งค่าผู้ใช้ปัจจุบันเป็น "31" คู่ขนาน "ForkJoinPool.commonPool-worker-3" เธรดเริ่มต้นและตั้งค่าปัจจุบัน ผู้ใช้เป็น "81" คู่ขนาน "ForkJoinPool.commonPool-worker-1" เธรดเริ่มต้นและตั้งค่าผู้ใช้ปัจจุบันเป็น "87" Start-main-> 62-> 62 Start-ForkJoinPool.commonPool-worker-2-> 31-> 31 Start-ForkJoinPool.commonPool-worker-3-> 81-> 81 Start-ForkJoinPool.commonPool-worker-1-> 87-> 87
  2. เธรดข้างต้นทั้งหมดนี้จะเข้าสู่โหมดสลีปเป็นเวลา 3 วินาที
  3. mainการดำเนินการสิ้นสุดและพิมพ์ผู้ใช้ปัจจุบันเป็น "62" ForkJoinPool.commonPool-worker-1การดำเนินการแบบคู่ขนานจะสิ้นสุดลงและพิมพ์ผู้ใช้ปัจจุบันเป็น "87" ForkJoinPool.commonPool-worker-2สิ้นสุดการดำเนินการแบบคู่ขนานและพิมพ์ผู้ใช้ปัจจุบันเป็น "31" ForkJoinPool.commonPool-worker-3การดำเนินการแบบคู่ขนานจะสิ้นสุดลงและพิมพ์ผู้ใช้ปัจจุบันเป็น "81"

การอนุมาน

เธรดพร้อมกันสามารถดึงข้อมูลผู้ใช้ที่ถูกต้องได้แม้ว่าจะมีการประกาศว่าเป็น "ThreadLocal สุดท้ายแบบคงที่" ก็ตาม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.