Bagaimana cara menggunakan ConcurrentLinkedQueue?

96

Bagaimana cara menggunakan ConcurrentLinkedQueuedi Java?
Dengan menggunakan ini LinkedQueue, apakah saya perlu khawatir tentang konkurensi dalam antrian? Atau apakah saya hanya perlu mendefinisikan dua metode (satu untuk mengambil elemen dari daftar dan yang lainnya untuk menambahkan elemen ke daftar)?
Catatan: jelas kedua metode ini harus disinkronkan. Baik?


EDIT: Apa yang saya coba lakukan adalah ini: Saya memiliki kelas (di Java) dengan satu metode untuk mengambil item dari antrian dan kelas lain dengan satu metode untuk menambahkan item ke antrian. Item yang ditambahkan dan diambil dari daftar adalah objek kelas saya sendiri.

Satu pertanyaan lagi: apakah saya perlu melakukan ini dalam metode hapus:

while (queue.size() == 0){ 
  wait(); 
  queue.poll();
}

Saya hanya memiliki satu konsumen dan satu produsen.

Ricardo Felgueiras
sumber
Terima kasih atas tanggapan atas pertanyaan mt. Apa yang saya coba lakukan adalah ini: saya memiliki kelas (di Java) dengan satu metode untuk mengambil item dari antrian dan kelas lain dengan satu metode untuk menambahkan item ke antrian. Item yang ditambahkan dan diambil dari daftar adalah objek kelas saya sendiri.
Ricardo Felgueiras
2
Anda harus mengedit pertanyaan Anda dan memasukkan klarifikasi ini ke dalam pertanyaan itu sendiri.
Adam Jaskiewicz

Jawaban:

157

Tidak, metode tidak perlu disinkronkan, dan Anda tidak perlu menentukan metode apa pun; mereka sudah ada di ConcurrentLinkedQueue, gunakan saja. ConcurrentLinkedQueue melakukan semua operasi penguncian dan lainnya yang Anda butuhkan secara internal; produser Anda menambahkan data ke antrian, dan konsumen Anda melakukan polling untuk itu.

Pertama, buat antrian Anda:

Queue<YourObject> queue = new ConcurrentLinkedQueue<YourObject>();

Sekarang, di mana pun Anda membuat objek producer / consumer, teruskan antrian sehingga mereka memiliki tempat untuk meletakkan objek mereka (Anda dapat menggunakan setter untuk ini, sebagai gantinya, tetapi saya lebih suka melakukan hal semacam ini dalam konstruktor):

YourProducer producer = new YourProducer(queue);

dan:

YourConsumer consumer = new YourConsumer(queue);

dan menambahkan barang ke dalamnya di produser Anda:

queue.offer(myObject);

dan mengambil barang di konsumen Anda (jika antrian kosong, poll () akan mengembalikan null, jadi periksa):

YourObject myObject = queue.poll();

Untuk info lebih lanjut lihat Javadoc

EDIT:

Jika Anda perlu memblokir menunggu antrian tidak kosong, Anda mungkin ingin menggunakan LinkedBlockingQueue , dan gunakan metode take (). Namun, LinkedBlockingQueue memiliki kapasitas maksimum (default ke Integer.MAX_VALUE, yang lebih dari dua miliar) dan dengan demikian mungkin atau mungkin tidak sesuai tergantung pada keadaan Anda.

Jika Anda hanya memiliki satu utas yang memasukkan barang ke antrean, dan utas lain mengambil barang dari antrean, ConcurrentLinkedQueue mungkin berlebihan. Lebih dari itu ketika Anda mungkin memiliki ratusan atau bahkan ribuan utas yang mengakses antrian pada saat yang bersamaan. Kebutuhan Anda mungkin akan dipenuhi dengan menggunakan:

Queue<YourObject> queue = Collections.synchronizedList(new LinkedList<YourObject>());

Nilai plusnya adalah ia mengunci instans (antrian), sehingga Anda dapat menyinkronkan antrian untuk memastikan atomicity operasi gabungan (seperti dijelaskan oleh Jared). Anda TIDAK DAPAT melakukan ini dengan ConcurrentLinkedQueue, karena semua operasi dilakukan TANPA mengunci instance (menggunakan variabel java.util.concurrent.atomic). Anda TIDAK perlu melakukan ini jika Anda ingin memblokir saat antrian kosong, karena poll () hanya akan mengembalikan null saat antrian kosong, dan poll () adalah atom. Periksa untuk melihat apakah poll () mengembalikan null. Jika ya, tunggu (), lalu coba lagi. Tidak perlu dikunci.

Akhirnya:

Sejujurnya, saya baru saja menggunakan LinkedBlockingQueue. Ini masih berlebihan untuk aplikasi Anda, tetapi kemungkinan besar itu akan berfungsi dengan baik. Jika kinerjanya tidak cukup (PROFIL!), Anda selalu dapat mencoba yang lain, dan itu berarti Anda tidak perlu berurusan dengan hal-hal yang disinkronkan:

BlockingQueue<YourObject> queue = new LinkedBlockingQueue<YourObject>();

queue.put(myObject); // Blocks until queue isn't full.

YourObject myObject = queue.take(); // Blocks until queue isn't empty.

Yang lainnya sama. Put mungkin tidak akan memblokir, karena Anda tidak mungkin memasukkan dua miliar objek ke dalam antrean.

Adam Jaskiewicz
sumber
Terimakasih atas tanggapan Anda. Satu pertanyaan lagi: apakah saya perlu melakukan ini dalam metode hapus: while (queue.size () == 0) wait (); queue.poll ();
Ricardo Felgueiras
Saya akan menjawabnya sebagai edit atas jawaban saya, karena ini cukup penting.
Adam Jaskiewicz
Karena menimbulkan kebingungan dalam pertanyaan lain, Collection.synchronizedListmengembalikan a Listyang tidak diimplementasikan Queue.
Tom Hawtin - tackline
@AdamJaskiewicz menggunakan ConcurrentLinkedQueueide yang bagus untuk Konsumen Produsen, saya mengacu pada posting ini stackoverflow.com/questions/1426754/…
rd22
37

Ini sebagian besar merupakan duplikat dari pertanyaan lain .

Inilah bagian dari jawaban itu yang relevan dengan pertanyaan ini:

Apakah saya perlu melakukan sinkronisasi sendiri jika saya menggunakan java.util.ConcurrentLinkedQueue?

Operasi atom pada koleksi bersamaan disinkronkan untuk Anda. Dengan kata lain, setiap panggilan individu ke antrian dijamin thread-safe tanpa tindakan apa pun dari Anda. Yang tidak dijamin aman utas adalah operasi apa pun yang Anda lakukan pada koleksi yang non-atomik.

Misalnya, ini threadsafe tanpa tindakan apa pun dari Anda:

queue.add(obj);

atau

queue.poll(obj);

Namun; panggilan non-atomic ke antrian tidak otomatis thread-safe. Misalnya, operasi berikut tidak otomatis threadsafe:

if(!queue.isEmpty()) {
   queue.poll(obj);
}

Yang terakhir bukanlah threadsafe, karena sangat mungkin bahwa antara waktu isEmpty dipanggil dan polling waktu dipanggil, thread lain akan menambahkan atau menghapus item dari antrian. Cara aman utas untuk melakukan ini adalah seperti ini:

synchronized(queue) {
    if(!queue.isEmpty()) {
       queue.poll(obj);
    }
}

Sekali lagi ... panggilan atomic ke antrian secara otomatis thread-safe. Panggilan non-atom tidak.

Jared
sumber
1
Satu-satunya penggunaan umum yang dapat saya lakukan adalah memblokir hingga antrian tidak kosong. Bukankah itu lebih baik dilayani dengan menggunakan implementasi BlockingQueue (take () adalah atom dan blok sampai ada sesuatu untuk dikonsumsi)?
Adam Jaskiewicz
6
Anda juga harus berhati-hati dengan itu. Tidak seperti synchronizedList, ConcurrentLinkedQueue tidak tersinkronisasi dengan sendirinya, jadi dalam kode Anda, produsen masih dapat menawarkan ke antrean saat Anda berada dalam blok tersinkronisasi.
Adam Jaskiewicz
Mengapa Anda menggabungkan queue.isEmpty () dan queue.poll ()? Tidak bisakah Anda melakukan polling dan memeriksa apakah hasilnya null? (pemahaman saya adalah bahwa jika Anda melakukan polling antrian kosong, hasilnya nol)
AjahnCharles
Saya setuju dengan semua yang ada di kode Anda kecuali blok kode terakhir. Sinkronisasi di ConcurrentLInkedQueue tidak menjamin apa pun karena panggilan lain tidak disinkronkan. Jadi mungkin ada "add (..)" yang terjadi dari utas lain antara "isEmpty ()" dan "poll (...)" Anda meskipun Anda telah menyinkronkan utas ini
Klitos G.
6

Gunakan polling untuk mendapatkan elemen pertama, dan tambahkan untuk menambahkan elemen terakhir baru. Itu saja, tidak ada sinkronisasi atau apa pun.

Hank Gay
sumber
6

Ini mungkin yang Anda cari dalam hal keamanan utas & "kecantikan" saat mencoba mengonsumsi semua yang ada di antrean:

for (YourObject obj = queue.poll(); obj != null; obj = queue.poll()) {
}

Ini akan menjamin bahwa Anda keluar saat antrian kosong, dan Anda terus mengeluarkan objek dari antrian tersebut selama tidak kosong.

marq
sumber
Sangat berguna untuk mengosongkan antrian. Terima kasih.
DevilCode
2

ConcurentLinkedQueue adalah implementasi bebas menunggu / kunci yang sangat efisien (lihat javadoc untuk referensi), jadi tidak hanya Anda tidak perlu menyinkronkan, tetapi antrian tidak akan mengunci apa pun, sehingga hampir secepat non-sinkronisasi (bukan utas aman) satu.

siddhadev
sumber
1

Gunakan saja seperti yang Anda lakukan pada koleksi non-konkuren. Class Concurrent [Collection] membungkus koleksi reguler sehingga Anda tidak perlu memikirkan tentang sinkronisasi akses.

Edit: ConcurrentLinkedList sebenarnya bukan hanya pembungkus, tetapi lebih merupakan implementasi bersamaan yang lebih baik. Apa pun itu, Anda tidak perlu khawatir tentang sinkronisasi.

Ben S
sumber
ConcurrentLinkedQueue tidak. Ini secara khusus dibangun dari bawah ke atas untuk akses bersamaan oleh banyak produsen dan konsumen. Sedikit lebih mewah daripada pembungkus sederhana yang dikembalikan oleh Collections.synchronized *
Adam Jaskiewicz
Ya, ada banyak hal konkurensi rapi yang ditambahkan di J2SE 5.
Adam Jaskiewicz