Pertanyaan awal yang Anda rujuk (sejauh, artikel wikipedia) mengatakan bahwa bangun palsu terjadi dalam implementasi pthread linux, sebagai efek samping dari proses yang ditandai . Dari pertanyaan Anda, menurut saya Anda melewatkan "sinyal" (yang merupakan metode komunikasi antar-proses linux) dengan Object.notify() (yang merupakan metode komunikasi antar-thread internal java).
Jika Anda ingin mengamati wakeup palsu -- Anda harus menjalankan program java Anda dan mencoba mengirimkan beberapa sinyal.
Saya mencoba tes sederhana di Linux, dengan mengirimkan sinyal proses Java sederhana (seperti QUIT, STOP, CONT, dll.). Ini tampaknya tidak menyebabkan bangun palsu.
Jadi (setidaknya bagi saya) masih belum jelas dalam kondisi apa sinyal Linux akan menyebabkan kebangkitan palsu di Java.
"Bangun palsu" adalah campuran dan mencakup apa saja detail implementasi di ranah itu. Oleh karena itu cukup sulit untuk mengetahui apa itu bangun palsu yang "nyata" dan mengapa yang lain "tidak nyata" - apalagi dari lapisan mana detail implementasi ini berasal. Pilih salah satu dari "kernel", "system library (libc)", "JVM", "Java standart library (rt.jar)" atau kerangka kerja khusus yang dibangun di atas tumpukan ini.
Program berikut menampilkan wakeup palsu menggunakan java.util.concurrent
hal-hal:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SpuriousWakeupRWLock {
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static int itemsReady;
public static void main(String[] args) throws Exception {
// let consumer 1 enter condition wait
new ConsumerOne().start();
Thread.sleep(500);
lock.lock();
try {
// let consumer 2 hit the lock
new ConsumerTwo().start();
Thread.sleep(500);
// make condition true and signal one (!) consumer
System.out.println("Producer: fill queue");
itemsReady = 1;
condition.signal();
Thread.sleep(500);
}
finally {
// release lock
lock.unlock();
}
System.out.println("Producer: released lock");
Thread.sleep(500);
}
abstract static class AbstractConsumer extends Thread {
@Override
public void run() {
lock.lock();
try {
consume();
} catch(Exception e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
abstract void consume() throws Exception;
}
static class ConsumerOne extends AbstractConsumer {
@Override
public void consume() throws InterruptedException {
if( itemsReady <= 0 ){ // usually this is "while"
System.out.println("One: Waiting...");
condition.await();
if( itemsReady <= 0 )
System.out.println("One: Spurious Wakeup! Condition NOT true!");
else {
System.out.println("One: Wakeup! Let's work!");
--itemsReady;
}
}
}
}
static class ConsumerTwo extends AbstractConsumer {
@Override
public void consume() {
if( itemsReady <= 0 )
System.out.println("Two: Got lock, but no work!");
else {
System.out.println("Two: Got lock and immediatly start working!");
--itemsReady;
}
}
}
}
Keluaran :
One: Waiting...
Producer: fill queue
Producer: released lock
Two: Got lock and immediatly start working!
One: Spurious Wakeup! Condition NOT true!
JDK yang digunakan adalah:
java version "1.6.0_20"
OpenJDK Runtime Environment (IcedTea6 1.9.9) (6b20-1.9.9-0ubuntu1~10.04.2)
OpenJDK 64-Bit Server VM (build 19.0-b09, mixed mode)
Ini didasarkan pada satu detail implementasi di java.util.concurrent
:Standar Lock
memiliki satu antrean tunggu, Condition
memiliki antrean lain. Jika kondisi diberi sinyal, utas yang diberi sinyal dipindahkan dari antrian kondisi ke antrian kunci. Detail penerapan:Dipindahkan di akhir antrean . Jika utas lain sudah menunggu di antrean kunci dan utas kedua ini tidak mengunjungi variabel kondisi, utas ini dapat "mencuri" sinyalnya. Jika penerapannya akan menempatkan utas pertama sebelum utas kedua, ini tidak akan terjadi. "Bonus" ini dapat/akan didasarkan pada fakta bahwa utas pertama telah mendapatkan kunci sekali dan bahwa waktu tunggu dalam kondisi terkait dengan kunci yang sama dikreditkan ke utas itu.
Saya mendefinisikan ini sebagai "palsu" karena
- kondisi telah ditandai hanya sekali,
- hanya satu utas yang dibangunkan oleh kondisi tersebut
- tetapi utas yang dibangunkan oleh kondisi ternyata tidak benar
- utas lainnya tidak pernah menyentuh kondisi tersebut dan karena itu "beruntung tapi polos"
- implementasi yang sedikit berbeda akan mencegah hal ini.
Poin terakhir ditunjukkan dengan kode ini menggunakan Object.wait()
:
public class SpuriousWakeupObject {
static Object lock = new Object();
static int itemsReady;
public static void main(String[] args) throws Exception {
// let consumer 1 enter condition wait
new ConsumerOne().start();
Thread.sleep(500);
// let consumer 2 hit the lock
synchronized (lock) {
new ConsumerTwo().start();
Thread.sleep(500);
// make condition true and signal one (!) consumer
System.out.println("Producer: fill queue");
itemsReady = 1;
lock.notify();
Thread.sleep(500);
} // release lock
System.out.println("Producer: released lock");
Thread.sleep(500);
}
abstract static class AbstractConsumer extends Thread {
@Override
public void run() {
try {
synchronized(lock){
consume();
}
} catch(Exception e){
e.printStackTrace();
}
}
abstract void consume() throws Exception;
}
static class ConsumerOne extends AbstractConsumer {
@Override
public void consume() throws InterruptedException {
if( itemsReady <= 0 ){ // usually this is "while"
System.out.println("One: Waiting...");
lock.wait();
if( itemsReady <= 0 )
System.out.println("One: Spurious Wakeup! Condition NOT true!");
else {
System.out.println("One: Wakeup! Let's work!");
--itemsReady;
}
}
}
}
static class ConsumerTwo extends AbstractConsumer {
@Override
public void consume() {
if( itemsReady <= 0 )
System.out.println("Two: Got lock, but no work!");
else {
System.out.println("Two: Got lock and immediatly start working!");
--itemsReady;
}
}
}
}
Keluaran:
One: Waiting...
Producer: fill queue
Producer: released lock
One: Wakeup! Let's work!
Two: Got lock, but no work!
Di sini implementasinya tampaknya seperti yang saya harapkan:Utas yang menggunakan kondisi tersebut dibangunkan terlebih dahulu.
Catatan akhir: Ide untuk prinsipnya berasal dari Mengapa java.util.concurrent.ArrayBlockingQueue menggunakan loop 'sementara' alih-alih 'jika' di sekitar panggilan ke menunggu ()? , meskipun interpretasi saya berbeda dan kodenya dari saya sendiri.
Anda tidak dapat memaksakan bangun palsu, tetapi pada utas yang sedang berjalan, bangun palsu tidak dapat dibedakan dari bangun biasa (sumber acara berbeda, tetapi acara itu sendiri sama)
Untuk mensimulasikan bangun palsu, cukup panggil notify();
Memanggil interrupt()
tidak cocok, karena hal itu menyetel bendera interupsi, dan setelah bangun palsu, bendera interupsi tidak atur