JavaRush /จาวาบล็อก /Random-TH /เห็นพ้องใน Java บทช่วยสอน - โครงสร้างที่ปลอดภัยสำหรับเธรด...
0xFF
ระดับ
Донецк

เห็นพ้องใน Java บทช่วยสอน - โครงสร้างที่ปลอดภัยสำหรับเธรด

เผยแพร่ในกลุ่ม
หลังจากดูความเสี่ยงหลักของการรันโปรแกรมแบบขนาน (เช่นอะตอมมิกซิตีหรือการมองเห็น ) เราจะดูการออกแบบคลาสบางส่วนที่จะช่วยป้องกันข้อผิดพลาดที่กล่าวมาข้างต้น โครงสร้างเหล่านี้บางส่วนสร้างออบเจ็กต์ที่ปลอดภัยสำหรับเธรด ซึ่งช่วยให้เราสามารถแบ่งปันระหว่างเธรดได้อย่างปลอดภัย ตัวอย่างเช่น เราจะดูวัตถุที่ไม่เปลี่ยนรูปและไร้สัญชาติ มุมมองอื่นๆ จะป้องกันไม่ให้เธรดที่แตกต่างกันแก้ไขข้อมูล เช่น ตัวแปรภายในของเธรด คุณสามารถดูซอร์สโค้ดทั้งหมดบน Github 1. วัตถุที่ไม่เปลี่ยนรูป วัตถุที่ไม่เปลี่ยนรูปมีสถานะ (มีข้อมูลที่แสดงถึงสถานะของวัตถุ) แต่จะถูกตั้งค่า ณ เวลาที่สร้างใน Constructor เมื่ออินสแตนซ์ของวัตถุถูกสร้างขึ้นและสถานะไม่สามารถเปลี่ยนแปลงได้ แม้ว่าเธรดสามารถสลับกันได้ แต่อ็อบเจ็กต์ยังคงมีสถานะที่เป็นไปได้หนึ่งสถานะ เนื่องจากฟิลด์ทั้งหมดเป็นแบบอ่านอย่างเดียว จึงไม่มีเธรดใดที่สามารถเปลี่ยนแปลงข้อมูลของออบเจ็กต์ได้ ด้วยเหตุนี้ สตรีมที่ไม่เปลี่ยนรูปจึงปลอดภัยสำหรับเธรดโดยเนื้อแท้ คลาสผลิตภัณฑ์แสดงให้เห็นถึงคลาสที่ไม่เปลี่ยนรูป โดยกรอกข้อมูลในช่องทั้งหมดในตัวสร้างและไม่มีช่องใดเปลี่ยนแปลง: public final class Product { private final String id; private final String name; private final double price; public Product(String id, String name, double price) { this.id = id; this.name = name; this.price = price; } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } public boolean equals(Object x) { if (this == x) return true; if (x == null) return false; if (this.getClass() != x.getClass()) return false; Product that = (Product) x; if (!this.id.equals(that.id)) return false; if (!this.name.equals(that.name)) return false; if (this.price != that.price) return false; return true; } public int hashCode() { int hash = 17; hash = 31 * hash + this.getId().hashCode(); hash = 31 * hash + this.getName().hashCode(); hash = 31 * hash + ((Double) this.getPrice()).hashCode(); return hash; } } ในบางกรณี การทำให้ช่องเป็นขั้นสุดท้ายอาจไม่เพียงพอ ตัวอย่างเช่น คลาส MutableProductไม่สามารถเปลี่ยนรูปได้ แม้ว่าฟิลด์ทั้งหมดจะถือเป็นที่สิ้นสุด: เหตุใดคลาสด้านบนจึงไม่เปลี่ยนรูป เหตุผลก็คือเราอนุญาตให้ดึงข้อมูลอ้างอิงจากชั้นเรียนได้ ช่อง ' หมวดหมู่ ' เป็นข้อมูลอ้างอิงที่ไม่แน่นอน ดังนั้นเมื่อได้รับแล้ว ลูกค้าจะสามารถแก้ไขได้ เพื่ออธิบายสิ่งนี้ ให้พิจารณาโปรแกรมต่อไปนี้: และเอาต์พุตคอนโซล: เนื่องจากฟิลด์ 'หมวดหมู่' ไม่สามารถเปลี่ยนแปลงได้และได้รับจากออบเจ็กต์ ไคลเอนต์จึงได้แก้ไขรายการนี้ มีการเปลี่ยนแปลงออบเจ็กต์ที่ไม่ควรเปลี่ยนรูป ส่งผลให้มีสถานะใหม่ หากคุณต้องการแสดงเนื้อหาของรายการ คุณสามารถใช้การแทนรายการที่ไม่เปลี่ยนรูปได้: 2. วัตถุไร้สัญชาติ วัตถุไร้สัญชาตินั้นคล้ายกับวัตถุที่ไม่เปลี่ยนรูป แต่ในกรณีนี้ วัตถุเหล่านั้นไม่มีสถานะ ไม่มีแม้แต่สถานะเดียว เมื่อออบเจ็กต์เป็นออบเจ็กต์ไร้สัญชาติ ก็ไม่จำเป็นต้องเก็บข้อมูลใดๆ ระหว่างการโทร เนื่องจากไม่มีสถานะอยู่ ไม่มีเธรดใดที่สามารถส่งผลกระทบต่อผลลัพธ์ของเธรดอื่นได้โดยการเรียกเมธอดของอ็อบเจ็กต์ ด้วยเหตุนี้ ออบเจ็กต์ไร้สัญชาติจึงปลอดภัยต่อเธรดโดยเนื้อแท้ คลาส ProductHandlerเป็นตัวอย่างของวัตถุประเภทนี้ ประกอบด้วยการดำเนินการหลายอย่างบนออบเจ็กต์ผลิตภัณฑ์ และจะไม่เก็บข้อมูลใดๆ ระหว่างการโทร ผลลัพธ์ของการดำเนินการไม่ขึ้นอยู่กับการโทรก่อนหน้าหรือข้อมูลที่เก็บไว้: ในวิธี sumCart นั้น ProductHandler จะแปลงรายการผลิตภัณฑ์เป็นอาร์เรย์เพื่อใช้ในแต่ละลูปเพื่อวนซ้ำองค์ประกอบทั้งหมด รายการตัววนซ้ำไม่ปลอดภัยสำหรับเธรด และอาจส่ง ConcurrentModificationExceptionหากมีการเปลี่ยนแปลงในระหว่างการวนซ้ำ คุณอาจเลือก กลยุทธ์อื่น ขึ้นอยู่กับความต้องการของคุณ 3. ตัวแปรภายในของเธรด ตัวแปรภายในของเธรดคือตัวแปรที่ถูกกำหนดไว้ภายในเธรด ไม่มีเธรดอื่นเห็นและจะไม่เปลี่ยนแปลง ประเภทแรกคือตัวแปรท้องถิ่น ในตัวอย่างด้านล่าง ผล รวม ของตัวแปร จะถูกเก็บไว้ในสแต็กของเธรด: public final class MutableProduct { private final String id; private final String name; private final double price; private final List categories = new ArrayList<>(); public MutableProduct(String id, String name, double price) { this.id = id; this.name = name; this.price = price; this.categories.add("A"); this.categories.add("B"); this.categories.add("C"); } public String getId() { return this.id; } public String getName() { return this.name; } public double getPrice() { return this.price; } public List getCategories() { return this.categories; } public List getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); } public String toString() { return new StringBuilder(this.id).append("-").append(this.name) .append(" (").append(this.price).append(")").toString(); } } public static void main(String[] args) { MutableProduct p = new MutableProduct("1", "a product", 43.00); System.out.println("Product categories"); for (String c : p.getCategories()) System.out.println(c); p.getCategories().remove(0); System.out.println("\nModified Product categories"); for (String c : p.getCategories()) System.out.println(c); } Product categories A B C Modified Product categories B C public List getCategoriesUnmodifiable() { return Collections.unmodifiableList(categories); } public class ProductHandler { private static final int DISCOUNT = 90; public Product applyDiscount(Product p) { double finalPrice = p.getPrice() * DISCOUNT / 100; return new Product(p.getId(), p.getName(), finalPrice); } public double sumCart(List cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; } } public double sumCart(List cart) { double total = 0.0; for (Product p : cart.toArray(new Product[0])) total += p.getPrice(); return total; } เพียงจำไว้ว่าหากคุณกำหนดการอ้างอิงแทนตัวแปรพื้นฐานแล้วส่งคืน การอ้างอิงนั้นจะออกจากขอบเขต คุณอาจไม่ทราบว่าลิงก์ที่ส่งคืนถูกสร้างขึ้นที่ไหน รหัสที่เรียก เมธอด sumCartสามารถจัดเก็บไว้ในฟิลด์คงที่และอนุญาตให้เข้าถึงได้โดยเธรดที่แตกต่างกัน ประเภทที่สองคือคลาส ThreadLocal คลาสนี้จัดให้มีหน่วยเก็บข้อมูลอิสระสำหรับแต่ละเธรด ค่าที่เก็บไว้ใน ThreadLocal นั้นใช้ได้กับโค้ดใด ๆ บนเธรดเดียวกัน คลาส ClientRequestId แสดงตัวอย่างการใช้คลาส ThreadLocale: คลาส ProductHandlerThreadLocalใช้ ClientRequestId เพื่อส่งคืน ID ที่สร้างขึ้นเดียวกันบนเธรดเดียวกัน: เมื่อดำเนินการเมธอดหลัก เอาต์พุตคอนโซลจะแสดง ID ที่แตกต่างกันสำหรับแต่ละเธรด ตัวอย่างเช่น: หากคุณกำลังจะใช้ ThreadLocale คุณจะต้องดูแลความเสี่ยงในการใช้งานบางประการเมื่อรวมเธรด (เช่นในแอปพลิเคชันเซิร์ฟเวอร์) คุณอาจได้รับหน่วยความจำรั่วหรือข้อมูลรั่วไหลระหว่างคำขอ ฉันจะไม่ลงรายละเอียดมากนักเกี่ยวกับเรื่องนี้เพราะว่า... บทความ “ วิธียิงตัวเองด้วย ThreadLocale ” แสดงให้เห็นอย่างชัดเจนว่าสิ่งนี้เกิดขึ้นได้อย่างไร 4. การใช้การซิงโครไนซ์ อีกวิธีหนึ่งในการให้การเข้าถึงออบเจ็กต์แบบเธรดที่ปลอดภัยคือการซิงโครไนซ์ หากเราซิงโครไนซ์การเข้าถึงข้อมูลอ้างอิงทั้งหมด จะมีเพียงวัตถุเธรดเดียวเท่านั้นที่จะเข้าถึงข้อมูลดังกล่าวในเวลาที่กำหนด เราจะหารือเรื่องนี้ในโพสต์ในอนาคต 5. บทสรุป เราพิจารณาวิธีการต่างๆ มากมายที่ช่วยให้คุณสามารถสร้างอ็อบเจ็กต์แบบง่ายที่สามารถเข้าถึงได้โดยหลายเธรด การป้องกันข้อผิดพลาดแบบมัลติเธรดนั้นยากกว่ามากหากอ็อบเจ็กต์สามารถมีได้หลายสถานะ ในทางกลับกัน หากออบเจ็กต์สามารถมีได้เพียงสถานะเดียวหรือไม่มีเลย เราก็ไม่จำเป็นต้องกังวลเกี่ยวกับหลายเธรดในการเข้าถึงออบเจ็กต์ในเวลาเดียวกัน ต้นฉบับ อยู่ที่นี่ public class ClientRequestId { private static final ThreadLocal id = new ThreadLocal () { @Override protected String initialValue() { return UUID.randomUUID().toString(); } }; public static String get() { return id.get(); } } public class ProductHandlerThreadLocal { //Same methods as in ProductHandler class public String generateOrderId() { return ClientRequestId.get(); } } T1 - 23dccaa2-8f34-43ec-bbfa-01cec5df3258 T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T2 - 936d0d9d-b507-46c0-a264-4b51ac3f527d T3 - 126b8359-3bcc-46b9-859a-d305aff22c7e ...
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION