ข้อมูลที่ใช้ร่วมกันเป็นหนึ่งในคุณสมบัติที่สำคัญที่สุดของโปรแกรมที่ทำงานพร้อมกัน นี่เป็นลักษณะที่สำคัญมากไม่ว่าจะเป็นวัตถุที่สืบทอดคลาสเธรดหรือวัตถุที่ใช้อินเทอร์เฟซที่รันได้
หากคุณสร้างอ็อบเจ็กต์ของคลาสที่ใช้อินเทอร์เฟซ Runnable และใช้อ็อบเจ็กต์นั้นเพื่อเริ่มชุดของเธรด เธรดเหล่านั้นทั้งหมดจะใช้คุณสมบัติเดียวกันร่วมกัน กล่าวอีกนัยหนึ่ง หากเธรดหนึ่งแก้ไขคุณสมบัติ เธรดที่เหลือทั้งหมดจะได้รับผลกระทบจากการเปลี่ยนแปลง
บางครั้ง เราชอบที่จะใช้มันเพียงอย่างเดียวภายในเธรด แทนที่จะแบ่งปันกับเธรดอื่นที่เริ่มต้นด้วยออบเจ็กต์เดียวกัน อินเทอร์เฟซการทำงานพร้อมกันของ Java จัดเตรียมกลไกที่ชัดเจนมากเพื่อให้เป็นไปตามข้อกำหนดนี้ ซึ่งเรียกว่าตัวแปรภายในเธรด ประสิทธิภาพของกลไกนี้ก็น่าประทับใจเช่นกัน
รู้ว่ามัน
ทำตามขั้นตอนที่แสดงด้านล่างเพื่อเสร็จสิ้นโปรแกรมตัวอย่าง
1. ขั้นแรก ติดตั้งโปรแกรมที่มีปัญหาข้างต้น สร้างคลาสชื่อ UnsafeTask และใช้อินเทอร์เฟซ Runnable ประกาศทรัพย์สินส่วนตัวประเภท java.util.Date ในชั้นเรียน รหัสมีดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
UnsafeTask คลาสสาธารณะใช้งาน Runnable {
วันที่เริ่มต้นส่วนตัว;
2. ใช้เมธอด run() ของ UnsafeTask ซึ่งจะสร้างอินสแตนซ์ของแอตทริบิวต์ startDate และส่งออกค่าไปยังคอนโซล เข้าสู่โหมดสลีปเป็นระยะเวลาแบบสุ่ม จากนั้นพิมพ์ค่าของแอตทริบิวต์ startDate ไปยังคอนโซลอีกครั้ง รหัสมีดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
@แทนที่
โมฆะสาธารณะวิ่ง () {
startDate = วันที่ใหม่ ();
System.out.printf("เริ่มต้นเธรด: %s : %s/n",
Thread.currentThread().getId(), startDate);
พยายาม {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} จับ (InterruptedException e) {
e.printStackTrace();
-
System.out.printf("เธรดเสร็จสิ้น: %s : %s/n",
Thread.currentThread().getId(), startDate);
-
3. ใช้งานคลาสหลักของโปรแกรมที่มีปัญหา สร้างคลาสด้วยเมธอด main() UnsafeMain ในเมธอด main() ให้สร้างวัตถุ UnsafeTask และใช้วัตถุนี้เพื่อสร้างวัตถุ 10 เธรดเพื่อเริ่ม 10 เธรด ตรงกลางของแต่ละเธรดให้นอนเป็นเวลา 2 วินาที รหัสมีดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
คลาสสาธารณะ UnsafeMain {
โมฆะคงที่สาธารณะ main (String [] args) {
งาน UnsafeTask = ใหม่ UnsafeTask();
สำหรับ (int i = 0; i <10; i++) {
เธรดเธรด = เธรดใหม่ (งาน);
เธรด.เริ่มต้น();
พยายาม {
TimeUnit.SECONDS.sleep (2);
} จับ (InterruptedException e) {
e.printStackTrace();
-
-
-
-
4. จากตรรกะข้างต้น แต่ละเธรดมีเวลาเริ่มต้นที่แตกต่างกัน อย่างไรก็ตาม ตามบันทึกเอาต์พุตด้านล่าง มีค่าเวลาที่เหมือนกันหลายค่า ดังต่อไปนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
หัวข้อเริ่มต้น: 9 : อาทิตย์ 29 กันยายน 23:31:08 CST 2013
หัวข้อเริ่มต้น: 10 : อาทิตย์ 29 กันยายน 23:31:10 CST 2013
หัวข้อเริ่มต้น: 11 : อาทิตย์ 29 กันยายน 23:31:12 CST 2013
หัวข้อเริ่มต้น: 12 : อาทิตย์ 29 กันยายน 23:31:14 CST 2013
กระทู้เสร็จ: 9 : อาทิตย์ 29 ก.ย. 23:31:14 CST 2013
หัวข้อเริ่มต้น: 13 : อาทิตย์ 29 กันยายน 23:31:16 CST 2013
กระทู้เสร็จ: 10 : อาทิตย์ ก.ย. 29 23:31:16 CST 2013
หัวข้อเริ่มต้น: 14 : อาทิตย์ 29 กันยายน 23:31:18 CST 2013
กระทู้เสร็จ: 11 : อาทิตย์ ก.ย. 29 23:31:18 CST 2013
หัวข้อเริ่มต้น: 15 : อาทิตย์ 29 กันยายน 23:31:20 CST 2013
กระทู้เสร็จสิ้น: 12: อาทิตย์ 29 กันยายน 23:31:20 CST 2013
หัวข้อเริ่มต้น: 16 : อาทิตย์ 29 กันยายน 23:31:22 CST 2013
หัวข้อเริ่มต้น: 17: อาทิตย์ 29 กันยายน 23:31:24 CST 2013
กระทู้เสร็จ: 17 : อาทิตย์ 29 ก.ย. 23:31:24 CST 2013
กระทู้เสร็จ: 15 : อาทิตย์ 29 ก.ย. 23:31:24 CST 2013
กระทู้เสร็จ: 13 : อาทิตย์ 29 ก.ย. 23:31:24 CST 2013
หัวข้อเริ่มต้น: 18 : อาทิตย์ 29 กันยายน 23:31:26 CST 2013
กระทู้เสร็จ: 14 : อาทิตย์ 29 ก.ย. 23:31:26 CST 2013
กระทู้เสร็จ: 18 : อาทิตย์ 29 ก.ย. 23:31:26 CST 2013
กระทู้เสร็จ: 16 : อาทิตย์ 29 ก.ย. 23:31:26 CST 2013
5. ดังที่แสดงไว้ข้างต้น เราจะใช้กลไกตัวแปรภายในเธรดเพื่อแก้ไขปัญหานี้
6. สร้างคลาสชื่อ SafeTask และใช้อินเทอร์เฟซ Runnable รหัสมีดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
SafeTask ระดับสาธารณะใช้งาน Runnable {
7. ประกาศวัตถุประเภท ThreadLocal<Date> เมื่อวัตถุถูกสร้างอินสแตนซ์ วิธีการinitialValue() จะถูกแทนที่ และค่าวันที่จริงจะถูกส่งกลับในวิธีนี้ รหัสมีดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
ThreadLocal<Date> ส่วนตัว startDate = new
ThreadLocal<วันที่>() {
@แทนที่
วันที่ป้องกัน defaultValue () {
คืนวันที่ใหม่ ();
-
-
8. นำเมธอด run() ไปใช้ของคลาส SafeTask เมธอดนี้เหมือนกับเมธอด run() ของ UnsafeTask ยกเว้นว่าเมธอดของแอตทริบิวต์ startDate จะได้รับการปรับเปลี่ยนเล็กน้อย รหัสมีดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
@แทนที่
โมฆะสาธารณะวิ่ง () {
System.out.printf("เริ่มต้นเธรด: %s : %s/n",
Thread.currentThread().getId(), startDate.get());
พยายาม {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} จับ (InterruptedException e) {
e.printStackTrace();
-
System.out.printf("เธรดเสร็จสิ้น: %s : %s/n",
Thread.currentThread().getId(), startDate.get());
-
9. คลาสหลักของตัวอย่างที่ปลอดภัยนี้โดยพื้นฐานแล้วเหมือนกับคลาสหลักของโปรแกรมที่ไม่ปลอดภัย ยกเว้นว่า UnsafeTask จำเป็นต้องแก้ไขเป็น SafeTask รหัสเฉพาะมีดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
SafeMain ระดับสาธารณะ {
โมฆะคงที่สาธารณะ main (String [] args) {
งาน SafeTask = SafeTask ใหม่ ();
สำหรับ (int i = 0; i <10; i++) {
เธรดเธรด = เธรดใหม่ (งาน);
เธรด.เริ่มต้น();
พยายาม {
TimeUnit.SECONDS.sleep (2);
} จับ (InterruptedException e) {
e.printStackTrace();
-
-
-
-
10. รันโปรแกรมและวิเคราะห์ความแตกต่างระหว่างอินพุตทั้งสอง
เพื่อให้การตั้งชื่อคลาสเป็นมาตรฐาน การตั้งชื่อคลาสหลักในบทความนี้จะแตกต่างจากข้อความต้นฉบับเล็กน้อย นอกจากนี้ โปรแกรมต้นฉบับและคำอธิบายข้อความไม่สอดคล้องกัน มันคงจะเป็นความผิดพลาดทางเสมียน
รู้ว่าทำไม
ด้านล่างนี้คือผลการดำเนินการของตัวอย่างการรักษาความปลอดภัย จากผลลัพธ์จะเห็นได้ง่ายว่าแต่ละเธรดมีค่าแอตทริบิวต์ startDate ที่เป็นของเธรดที่เกี่ยวข้อง อินพุตของโปรแกรมมีดังนี้:
คัดลอกรหัสรหัสดังต่อไปนี้:
หัวข้อเริ่มต้น: 9 : อาทิตย์ 29 กันยายน 23:52:17 CST 2013
หัวข้อเริ่มต้น: 10 : อาทิตย์ 29 กันยายน 23:52:19 CST 2013
หัวข้อเริ่มต้น: 11 : อาทิตย์ 29 กันยายน 23:52:21 CST 2013
กระทู้เสร็จสิ้น: 10: อาทิตย์ 29 กันยายน 23:52:19 CST 2013
หัวข้อเริ่มต้น: 12 : อาทิตย์ 29 กันยายน 23:52:23 CST 2013
กระทู้เสร็จ: 11 : อาทิตย์ 29 ก.ย. 23:52:21 CST 2013
หัวข้อเริ่มต้น: 13 : อาทิตย์ 29 กันยายน 23:52:25 CST 2013
กระทู้เสร็จ: 9 : อาทิตย์ 29 ก.ย. 23:52:17 CST 2013
หัวข้อเริ่มต้น: 14 : อาทิตย์ 29 กันยายน 23:52:27 CST 2013
หัวข้อเริ่มต้น: 15 : อาทิตย์ 29 กันยายน 23:52:29 CST 2013
กระทู้เสร็จ: 13 : อาทิตย์ 29 ก.ย. 23:52:25 CST 2013
หัวข้อเริ่มต้น: 16 : อาทิตย์ 29 กันยายน 23:52:31 CST 2013
กระทู้เสร็จ: 14 : อาทิตย์ 29 ก.ย. 23:52:27 CST 2013
หัวข้อเริ่มต้น: 17: อาทิตย์ 29 กันยายน 23:52:33 CST 2013
กระทู้เสร็จสิ้น: 12: อาทิตย์ 29 กันยายน 23:52:23 CST 2013
กระทู้เสร็จ: 16 : อาทิตย์ 29 ก.ย. 23:52:31 CST 2013
กระทู้เสร็จ: 15 : อาทิตย์ ก.ย. 29 23:52:29 CST 2013
หัวข้อเริ่มต้น: 18 : อาทิตย์ 29 กันยายน 23:52:35 CST 2013
กระทู้เสร็จ: 17 : อาทิตย์ 29 ก.ย. 23:52:33 CST 2013
กระทู้เสร็จ: 18 : อาทิตย์ 29 ก.ย. 23:52:35 CST 2013
ตัวแปรท้องถิ่นของเธรดเก็บสำเนาของคุณสมบัติสำหรับแต่ละเธรด คุณสามารถใช้เมธอด get() ของ ThreadLocal เพื่อรับค่าของตัวแปร และใช้เมธอด set() เพื่อตั้งค่าของตัวแปร หากมีการเข้าถึงตัวแปรภายในเธรดเป็นครั้งแรกและยังไม่ได้กำหนดค่าตัวแปร ระบบจะเรียกเมธอด defaultValue() เพื่อเริ่มต้นค่าสำหรับแต่ละเธรด
ไม่มีวันสิ้นสุด
คลาThreadLocalยังมีremove()วิธีการลบค่าตัวแปรท้องถิ่นที่เก็บไว้ในเธรดที่เรียกวิธีนี้
นอกจากนี้ Java Concurrency API ยังมีคลาส InheritableThreadLocal ซึ่งอนุญาตให้เธรดลูกรับค่าเริ่มต้นของตัวแปรโลคัลเธรดที่สืบทอดได้ทั้งหมดเพื่อรับค่าที่เป็นของเธรดพาเรนต์ หากเธรด A มีตัวแปรโลคัลเธรด เมื่อเธรด A สร้างเธรด B เธรด B จะมีตัวแปรโลคัลเธรดเดียวกันกับเธรด A คุณยังสามารถแทนที่ childValue() เพื่อเริ่มต้นตัวแปรโลคัลเธรดของเธรดลูกได้ วิธีนี้จะยอมรับค่าของตัวแปรภายในเธรดที่ส่งผ่านเป็นพารามิเตอร์จากเธรดหลัก
ใช้หลักคำสอน
บทความนี้แปลมาจาก "Java 7 Concurrency Cookbook" (D Gua Ge ขโมยมาในชื่อ "Java7 Concurrency Example Collection") และใช้เป็นสื่อการเรียนรู้เท่านั้น ห้ามนำไปใช้เพื่อวัตถุประสงค์ทางการค้าใดๆ โดยไม่ได้รับอนุญาต
ความสำเร็จเล็กๆ น้อยๆ
ด้านล่างนี้เป็นเวอร์ชันสมบูรณ์ของโค้ดทั้งหมดที่รวมอยู่ในตัวอย่างในส่วนนี้
รหัสที่สมบูรณ์ของคลาส UnsafeTask:
คัดลอกรหัสรหัสดังต่อไปนี้:
แพ็คเกจ com.diguage.books.concurrencycookbook.chapter1.recipe9;
นำเข้า java.util.Date;
นำเข้า java.util.concurrent.TimeUnit;
-
* ตัวอย่างที่ไม่สามารถรับประกันความปลอดภัยของด้ายได้
* วันที่: 23-09-2013
* เวลา: 23:58 น
-
UnsafeTask คลาสสาธารณะใช้งาน Runnable {
วันที่เริ่มต้นส่วนตัว;
@แทนที่
โมฆะสาธารณะวิ่ง () {
startDate = วันที่ใหม่ ();
System.out.printf("เริ่มต้นเธรด: %s : %s/n",
Thread.currentThread().getId(), startDate);
พยายาม {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} จับ (InterruptedException e) {
e.printStackTrace();
-
System.out.printf("เธรดเสร็จสิ้น: %s : %s/n",
Thread.currentThread().getId(), startDate);
-
-
รหัสที่สมบูรณ์ของคลาส UnsafeMain:
คัดลอกรหัสรหัสดังต่อไปนี้:
แพ็คเกจ com.diguage.books.concurrencycookbook.chapter1.recipe9;
นำเข้า java.util.concurrent.TimeUnit;
-
* ตัวอย่างเธรดที่ไม่ปลอดภัย
* วันที่: 24-09-2013
* เวลา: 00:04 น
-
คลาสสาธารณะ UnsafeMain {
โมฆะคงที่สาธารณะ main (String [] args) {
งาน UnsafeTask = ใหม่ UnsafeTask();
สำหรับ (int i = 0; i <10; i++) {
เธรดเธรด = เธรดใหม่ (งาน);
เธรด.เริ่มต้น();
พยายาม {
TimeUnit.SECONDS.sleep (2);
} จับ (InterruptedException e) {
e.printStackTrace();
-
-
-
-
รหัสที่สมบูรณ์ของคลาส SafeTask:
คัดลอกรหัสรหัสดังต่อไปนี้:
แพ็คเกจ com.diguage.books.concurrencycookbook.chapter1.recipe9;
นำเข้า java.util.Date;
นำเข้า java.util.concurrent.TimeUnit;
-
* ใช้ตัวแปรโลคัลเธรดเพื่อความปลอดภัยของเธรด
* วันที่: 29-09-2556
* เวลา: 23:34 น
-
SafeTask ระดับสาธารณะใช้งาน Runnable {
ThreadLocal<Date> แบบคงที่ส่วนตัว startDate = ใหม่
ThreadLocal<วันที่>() {
@แทนที่
วันที่ป้องกัน defaultValue () {
คืนวันที่ใหม่ ();
-
-
@แทนที่
โมฆะสาธารณะวิ่ง () {
System.out.printf("เริ่มต้นเธรด: %s : %s/n",
Thread.currentThread().getId(), startDate.get());
พยายาม {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} จับ (InterruptedException e) {
e.printStackTrace();
-
System.out.printf("เธรดเสร็จสิ้น: %s : %s/n",
Thread.currentThread().getId(), startDate.get());
-
-
รหัสที่สมบูรณ์ของคลาส SafeMain:
คัดลอกรหัสรหัสดังต่อไปนี้:
แพ็คเกจ com.diguage.books.concurrencycookbook.chapter1.recipe9;
นำเข้า java.util.concurrent.TimeUnit;
-
* ตัวอย่างเธรดที่ปลอดภัย
* วันที่: 24-09-2013
* เวลา: 00:04 น
-
SafeMain ระดับสาธารณะ {
โมฆะคงที่สาธารณะ main (String [] args) {
งาน SafeTask = SafeTask ใหม่ ();
สำหรับ (int i = 0; i <10; i++) {
เธรดเธรด = เธรดใหม่ (งาน);
เธรด.เริ่มต้น();
พยายาม {
TimeUnit.SECONDS.sleep (2);
} จับ (InterruptedException e) {
e.printStackTrace();
-
-
-
-