ฉันจะทำให้ ArrayList Thread-Safe ได้อย่างไร แนวทางอื่นในการแก้ปัญหาใน Java?


91

ฉันมี ArrayList ที่ฉันต้องการใช้เพื่อเก็บวัตถุ RaceCar ที่ขยายคลาสเธรดทันทีที่ดำเนินการเสร็จสิ้น คลาสที่เรียกว่า Race จะจัดการ ArrayList นี้โดยใช้วิธีการเรียกกลับที่อ็อบเจ็กต์ RaceCar เรียกใช้เมื่อดำเนินการเสร็จสิ้น วิธีการโทรกลับ addFinisher (RaceCar finisher) เพิ่มวัตถุ RaceCar ไปยัง ArrayList สิ่งนี้ควรจะให้ลำดับที่เธรดดำเนินการเสร็จสิ้น

ฉันรู้ว่า ArrayList ไม่ซิงโครไนซ์ดังนั้นจึงไม่ปลอดภัยต่อเธรด ฉันลองใช้เมธอด Collections.synchronizedCollection (c Collection) โดยส่ง ArrayList ใหม่และกำหนด Collection ที่ส่งคืนไปยัง ArrayList อย่างไรก็ตามสิ่งนี้ทำให้ฉันมีข้อผิดพลาดของคอมไพเลอร์:

Race.java:41: incompatible types
found   : java.util.Collection
required: java.util.ArrayList
finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars));

นี่คือรหัสที่เกี่ยวข้อง:

public class Race implements RaceListener {
    private Thread[] racers;
    private ArrayList finishingOrder;

    //Make an ArrayList to hold RaceCar objects to determine winners
    finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars));

    //Fill array with RaceCar objects
    for(int i=0; i<numberOfRaceCars; i++) {
    racers[i] = new RaceCar(laps, inputs[i]);

        //Add this as a RaceListener to each RaceCar
        ((RaceCar) racers[i]).addRaceListener(this);
    }

    //Implement the one method in the RaceListener interface
    public void addFinisher(RaceCar finisher) {
        finishingOrder.add(finisher);
    }

สิ่งที่ฉันต้องรู้คือฉันใช้แนวทางที่ถูกต้องหรือไม่ถ้าไม่ฉันควรใช้อะไรเพื่อทำให้รหัสเธรดของฉันปลอดภัย ขอบคุณสำหรับความช่วยเหลือ!


2
(โปรดทราบว่าListอินเทอร์เฟซยังไม่สมบูรณ์เพียงพอที่จะเป็นประโยชน์อย่างมากในการใช้งานมัลติเธรด)
Tom Hawtin - แทคไลน์

3
ฉันแค่อยากจะชี้ให้เห็นว่าหากไม่มีCollections.synchronizedList()เราจะมีสภาพการแข่งขันจริงที่นี่: P
Dylan Watson

ตรวจสอบลิงค์นี้programmerzdojo.com/java-tutorials/…
rishi007bansod

คำตอบ:


149

ใช้Collections.synchronizedList().

เช่น:

Collections.synchronizedList(new ArrayList<YourClassNameHere>())

2
ขอบคุณ! ฉันไม่แน่ใจว่าทำไมฉันไม่คิดจะใช้แค่ Vector เพราะฉันจำได้ว่าอ่านที่ไหนสักแห่งที่มันซิงโครไนซ์
ericso

32
อาจไม่ใช่ความคิดที่ดีในการทำงานกับชั้นเรียนที่ถูกกำหนดให้เลิกใช้งาน
frandevel

1
แม้ว่า Vector จะค่อนข้างเก่าและไม่มีการสนับสนุนคอลเล็กชัน แต่ก็ไม่ได้เลิกใช้งาน น่าจะดีกว่าถ้าใช้ Collections.synchronizedList () เหมือนที่คนอื่น ๆ พูดไว้ที่นี่
Asturio

14
-1 สำหรับความคิดเห็น เวกเตอร์ไม่เลิกใช้งานและไม่มีการสนับสนุนคอลเล็กชันอย่างไร มันดำเนินรายการ javadoc สำหรับ Vector ระบุไว้โดยเฉพาะว่า: "ในขณะที่แพลตฟอร์ม Java 2 v1.2 คลาสนี้ได้รับการติดตั้งเพิ่มเติมเพื่อใช้งานอินเทอร์เฟซรายการทำให้เป็นสมาชิกของ Java Collections Framework ซึ่งแตกต่างจากการใช้งานคอลเลกชันใหม่ Vector จะถูกซิงโครไนซ์" อาจมีเหตุผลที่ดีที่ไม่ใช้ Vector (หลีกเลี่ยงการซิงโครไนซ์เปลี่ยนการนำไปใช้งาน) แต่การ "ล้าสมัย" หรือ "ไม่ทันสมัย" ไม่ใช่หนึ่งในนั้น
fool4jesus

1
ใช้วิธีการด้านล่าง: Collections.synchronizedList (รายการ); Collections.synchronizedSet (ชุด); Collections.synchronizedMap (แผนที่); วิธีการข้างต้นใช้การรวบรวมเป็นพารามิเตอร์และส่งคืนคอลเล็กชันประเภทเดียวกันซึ่งซิงโครไนซ์และเธรดปลอดภัย
Sameer Kazi

35

เปลี่ยน

private ArrayList finishingOrder;

//Make an ArrayList to hold RaceCar objects to determine winners
finishingOrder = Collections.synchronizedCollection(new ArrayList(numberOfRaceCars)

ถึง

private List finishingOrder;

//Make an ArrayList to hold RaceCar objects to determine winners
finishingOrder = Collections.synchronizedList(new ArrayList(numberOfRaceCars)

List เป็นประเภทพิเศษของ ArrayList ดังนั้นคุณต้องระบุสิ่งนั้น

มิฉะนั้นสิ่งที่คุณทำก็ดูดี ตัวเลือกอื่นคือคุณสามารถใช้ Vector ซึ่งจะซิงโครไนซ์ แต่นี่อาจเป็นสิ่งที่ฉันควรทำ


1
หรือListอาจจะมีประโยชน์มากขึ้น หรือList<RaceCar>.
Tom Hawtin - แท็กไลน์

จุดดีทำให้เป็นส่วนตัว List FinishingOrder = Collections.synchronizedList (... )
Reverend Gonzo

ฉันลองสิ่งนี้และตอนนี้คอมไพเลอร์บ่นว่าฉันเรียกเมธอด ArrayList บนคอลเลกชัน: //Print out winner System.out.println("The Winner is " + ((RaceCar) finishingOrder.get(0)).toString() + "!"); มันบอกว่าไม่พบเมธอด get (0) ความคิด?
ericso

ขออภัยเกี่ยวกับการลบและเพิ่มความคิดเห็นของฉันใหม่ ฉันพยายามทำให้ไฮไลต์ทำงานโดยใช้แบ็กติก ฉันได้รับ OCD เกี่ยวกับสิ่งของประเภทนั้น
ericso

ไม่นั่นไม่ได้ผล จะไม่ส่งคอลเล็กชันไปยังรายการ: Race.java:41: พบประเภทที่เข้ากันไม่ได้: java.util.Collection required: java.util.List finishOrder = Collections.synchronizedCollection (ArrayList ใหม่ (numberOfRaceCars));
ericso

12

CopyOnWriteArrayList

ใช้CopyOnWriteArrayListคลาส นี่คือเวอร์ชันที่ปลอดภัยของเธรดของArrayList.


5
คิดสองครั้งเมื่อพิจารณาชั้นเรียนนี้ ในการอ้างถึงคลาส doc: "โดยปกติจะมีค่าใช้จ่ายสูงเกินไป แต่อาจมีประสิทธิภาพมากกว่าทางเลือกอื่นเมื่อการดำเนินการข้ามผ่านมีจำนวนมากกว่าการกลายพันธุ์อย่างมากและมีประโยชน์เมื่อคุณไม่สามารถหรือไม่ต้องการซิงโครไนซ์การข้ามผ่าน แต่จำเป็นต้องป้องกันการรบกวนระหว่างเธรดที่เกิดขึ้นพร้อมกัน .” ดูความแตกต่างระหว่าง CopyOnWriteArrayList และ synchronizedList
Basil Bourque

1
คลาสนี้เข้ามามีบทบาทเมื่อคุณไม่ค่อยแก้ไขรายการ แต่มักจะวนซ้ำองค์ประกอบ เช่นเมื่อคุณมีกลุ่มผู้ฟัง คุณลงทะเบียนพวกเขาแล้วคุณก็วนซ้ำอีกมาก ... หากคุณไม่ต้องการอินเทอร์เฟซรายการอย่างชัดเจน แต่แก้ไขและอ่านการดำเนินการให้พร้อมกันให้พิจารณาConcurrentLinkedQueue
59

7

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

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


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

5

คุณยังสามารถใช้synchronizedคำสำคัญสำหรับaddFinisherวิธีการเช่นนี้

    //Implement the one method in the RaceListener interface
    public synchronized void addFinisher(RaceCar finisher) {
        finishingOrder.add(finisher);
    }

ดังนั้นคุณสามารถใช้ ArrayList เพิ่มเมธอดเธรดได้อย่างปลอดภัยด้วยวิธีนี้


4
ดี แต่ถ้าคุณมีสองวิธี: addFinisher และ delFinisher? ทั้งสองวิธีนี้ปลอดภัยต่อเธรด แต่เนื่องจากทั้งสองเข้าถึง ArrayList เดียวกันคุณจะยังคงประสบปัญหา
รอบ

1
@masi จากนั้นคุณจะซิงโครไนซ์final Objectแทนทุกครั้งที่คุณเข้าถึงCollectionด้วยวิธีใดก็ได้
mkuech

2

เมื่อใดก็ตามที่คุณต้องการใช้อ็อบเจ็กต์การรวบรวมมดเวอร์ชันปลอดภัยของเธรดให้ใช้ความช่วยเหลือของแพ็คเกจjava.util.concurrent. * มีเกือบทั้งหมดของวัตถุคอลเลกชันที่ไม่ซิงโครไนซ์พร้อมกัน เช่นสำหรับ ArrayList คุณมี java.util.concurrent.CopyOnWriteArrayList

คุณสามารถทำ Collections.synchronizedCollection (วัตถุคอลเลกชันใดก็ได้) แต่จำการซิงค์แบบคลาสสิกนี้ไว้ เทคนิคมีราคาแพงและมาพร้อมกับค่าใช้จ่ายในการแสดง แพ็คเกจ java.util.concurrent. * มีราคาไม่แพงและจัดการประสิทธิภาพได้ดีขึ้นโดยใช้กลไกเช่น

คัดลอกเมื่อเขียนเปรียบเทียบและสลับล็อคตัวทำซ้ำสแนปชอต ฯลฯ

ดังนั้นต้องการบางอย่างจากแพ็คเกจ java.util.concurrent. *


1

คุณยังสามารถใช้เป็น Vector แทนได้เนื่องจากเวกเตอร์มีความปลอดภัยของเธรดและอาร์เรย์ไม่ได้ แม้ว่าเวกเตอร์จะเก่า แต่ก็สามารถแก้ไขจุดประสงค์ของคุณได้อย่างง่ายดาย

แต่คุณสามารถทำให้ Arraylist ของคุณซิงโครไนซ์เหมือนรหัสที่ให้ไว้นี้:

Collections.synchronizedList(new ArrayList(numberOfRaceCars())); 

-1

คุณสามารถเปลี่ยนจาก ArrayList เป็นประเภท Vector ซึ่งทุกวิธีจะซิงโครไนซ์

private Vector finishingOrder;
//Make a Vector to hold RaceCar objects to determine winners
finishingOrder = new Vector(numberOfRaceCars);

5
หากคุณกำลังจะแนะนำให้ใช้คอลเล็กชันอื่น Vector อาจเป็นทางเลือกที่ไม่ดี เป็นคอลเลคชันดั้งเดิมซึ่งได้รับการติดตั้งเพิ่มเติมในการออกแบบ Java Collections Framework ใหม่ ฉันแน่ใจว่ามีตัวเลือกที่ดีกว่าในแพ็คเกจ java.until.concurrent
Edwin Dalorzo
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.