CodeGym/Blog Java/rawak/Lebih baik bersama: Java dan kelas Thread. Bahagian III -...
John Squirrels
Tahap
San Francisco

Lebih baik bersama: Java dan kelas Thread. Bahagian III - Interaksi

Diterbitkan dalam kumpulan
Gambaran keseluruhan ringkas tentang butir-butir bagaimana benang berinteraksi. Sebelum ini, kami melihat cara utas disegerakkan antara satu sama lain. Kali ini kita akan menyelami masalah yang mungkin timbul apabila benang berinteraksi, dan kita akan bercakap tentang cara mengelakkannya. Kami juga akan menyediakan beberapa pautan berguna untuk kajian yang lebih mendalam. Lebih baik bersama: Java dan kelas Thread.  Bahagian III — Interaksi - 1

pengenalan

Jadi, kita tahu bahawa Java mempunyai benang. Anda boleh membaca tentang itu dalam ulasan bertajuk Better together: Java and the Thread class. Bahagian I — Benang pelaksanaan . Dan kami meneroka fakta bahawa benang boleh menyegerakkan antara satu sama lain dalam ulasan bertajuk Better together: Java and the Thread class. Bahagian II — Penyegerakan . Sudah tiba masanya untuk bercakap tentang cara utas berinteraksi antara satu sama lain. Bagaimanakah mereka berkongsi sumber yang dikongsi? Apakah masalah yang mungkin timbul di sini? Lebih baik bersama: Java dan kelas Thread.  Bahagian III — Interaksi - 2

Jalan buntu

Masalah yang paling menakutkan adalah kebuntuan. Kebuntuan ialah apabila dua atau lebih utas menunggu yang lain secara kekal. Kami akan mengambil contoh daripada halaman web Oracle yang menerangkan kebuntuan :
public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s bowed to me!%n",
                    this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s bowed back to me!%n",
                    this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");
        new Thread(() -> alphonse.bow(gaston)).start();
        new Thread(() -> gaston.bow(alphonse)).start();
    }
}
Kebuntuan mungkin tidak berlaku di sini buat kali pertama, tetapi jika program anda hang, maka sudah tiba masanya untuk dijalankan jvisualvm: Lebih baik bersama: Java dan kelas Thread.  Bahagian III — Interaksi - 3Dengan pemalam JVisualVM dipasang (melalui Alat -> Pemalam), kita boleh melihat di mana kebuntuan itu berlaku:
"Thread-1" - Thread t@12
   java.lang.Thread.State: BLOCKED
	at Deadlock$Friend.bowBack(Deadlock.java:16)
	- waiting to lock <33a78231> (a Deadlock$Friend) owned by "Thread-0" t@11
Benang 1 sedang menunggu kunci dari benang 0. Mengapakah itu berlaku? Thread-1mula berjalan dan melaksanakan Friend#bowkaedah. Ia ditandakan dengan synchronizedkata kunci, yang bermaksud kami memperoleh monitor untuk this(objek semasa). Input kaedah adalah rujukan kepada Friendobjek lain. Sekarang, Thread-1mahu melaksanakan kaedah pada yang lain Friend, dan mesti memperoleh kuncinya untuk berbuat demikian. Tetapi jika benang lain (dalam kes ini Thread-0) berjaya memasuki bow()kaedah, maka kunci telah diperoleh dan Thread-1menungguThread-0, dan begitu juga sebaliknya. Ini kebuntuan tidak dapat diselesaikan, dan kami memanggilnya kebuntuan. Seperti cengkaman maut yang tidak boleh dilepaskan, kebuntuan adalah saling menghalang yang tidak boleh dipatahkan. Untuk penjelasan lain tentang kebuntuan, anda boleh menonton video ini: Deadlock dan Livelock Explained .

Livelock

Jika ada kebuntuan, adakah juga hidupan? Ya, ada :) Livelock berlaku apabila utas secara luaran kelihatan seperti hidup, tetapi mereka tidak dapat melakukan apa-apa, kerana syarat yang diperlukan untuk mereka meneruskan kerja mereka tidak dapat dipenuhi. Pada asasnya, livelock adalah serupa dengan kebuntuan, tetapi benang tidak "bergantung" menunggu monitor. Sebaliknya, mereka selama-lamanya melakukan sesuatu. Sebagai contoh:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class App {
    public static final String ANSI_BLUE = "\u001B[34m";
    public static final String ANSI_PURPLE = "\u001B[35m";

    public static void log(String text) {
        String name = Thread.currentThread().getName(); // Like "Thread-1" or "Thread-0"
        String color = ANSI_BLUE;
        int val = Integer.valueOf(name.substring(name.lastIndexOf("-") + 1)) + 1;
        if (val != 0) {
            color = ANSI_PURPLE;
        }
        System.out.println(color + name + ": " + text + color);
        try {
            System.out.println(color + name + ": wait for " + val + " sec" + color);
            Thread.currentThread().sleep(val * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Lock first = new ReentrantLock();
        Lock second = new ReentrantLock();

        Runnable locker = () -> {
            boolean firstLocked = false;
            boolean secondLocked = false;
            try {
                while (!firstLocked || !secondLocked) {
                    firstLocked = first.tryLock(100, TimeUnit.MILLISECONDS);
                    log("First Locked: " + firstLocked);
                    secondLocked = second.tryLock(100, TimeUnit.MILLISECONDS);
                    log("Second Locked: " + secondLocked);
                }
                first.unlock();
                second.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        new Thread(locker).start();
        new Thread(locker).start();
    }
}
Kejayaan kod ini bergantung pada susunan di mana penjadual benang Java memulakan benang. Jika Thead-1bermula dahulu, maka kita akan mendapat binatang ternakan:
Thread-1: First Locked: true
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
Thread-0: Second Locked: true
Thread-0: wait for 1 sec
Thread-1: Second Locked: false
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
...
Seperti yang anda lihat daripada contoh, kedua-dua utas cuba memperoleh kedua-dua kunci secara bergilir-gilir, tetapi mereka gagal. Tetapi, mereka tidak berada dalam kebuntuan. Secara luaran, semuanya baik-baik saja dan mereka menjalankan tugas mereka. Lebih baik bersama: Java dan kelas Thread.  Bahagian III — Interaksi - 4Menurut JVisualVM, kita melihat tempoh tidur dan tempoh taman (ini ialah apabila benang cuba memperoleh kunci — ia memasuki keadaan taman, seperti yang kita bincangkan sebelum ini apabila kita bercakap tentang penyegerakan benang ) . Anda boleh melihat contoh livelock di sini: Java - Thread Livelock .

Kebuluran

Sebagai tambahan kepada kebuntuan dan gangguan hidup, terdapat satu lagi masalah yang boleh berlaku semasa multithreading: kelaparan. Fenomena ini berbeza daripada bentuk sekatan sebelumnya kerana utas tidak disekat — mereka tidak mempunyai sumber yang mencukupi. Akibatnya, sementara beberapa utas mengambil semua masa pelaksanaan, yang lain tidak dapat dijalankan: Lebih baik bersama: Java dan kelas Thread.  Bahagian III — Interaksi - 5

https://www.logicbig.com/

Anda boleh melihat contoh hebat di sini: Java - Thread Starvation and Fairness . Contoh ini menunjukkan perkara yang berlaku dengan benang semasa kelaparan dan cara satu perubahan kecil daripada Thread.sleep()kepada Thread.wait()membolehkan anda mengagihkan beban secara sama rata. Lebih baik bersama: Java dan kelas Thread.  Bahagian III — Interaksi - 6

Keadaan perlumbaan

Dalam multithreading, terdapat perkara seperti "keadaan perlumbaan". Fenomena ini berlaku apabila utas berkongsi sumber, tetapi kod itu ditulis dengan cara yang tidak memastikan perkongsian yang betul. Lihat satu contoh:
public class App {
    public static int value = 0;

    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i < 10000; i++) {
                int oldValue = value;
                int newValue = ++value;
                if (oldValue + 1 != newValue) {
                    throw new IllegalStateException(oldValue + " + 1 = " + newValue);
                }
            }
        };
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}
Kod ini mungkin tidak menghasilkan ralat pada kali pertama. Apabila ia berlaku, ia mungkin kelihatan seperti ini:
Exception in thread "Thread-1" java.lang.IllegalStateException: 7899 + 1 = 7901
	at App.lambda$main$0(App.java:13)
	at java.lang.Thread.run(Thread.java:745)
Seperti yang anda lihat, sesuatu telah berlaku semasa newValuediberi nilai. newValueterlalu besar. Oleh kerana keadaan perlumbaan, salah satu daripada benang berjaya menukar pembolehubah valueantara dua kenyataan. Ternyata ada perlumbaan antara benang. Sekarang fikirkan betapa pentingnya untuk tidak membuat kesilapan yang sama dengan urus niaga kewangan... Contoh dan rajah juga boleh dilihat di sini: Kod untuk mensimulasikan keadaan perlumbaan dalam benang Java .

Tidak menentu

Bercakap tentang interaksi urutan, volatilekata kunci patut disebut. Mari lihat contoh mudah:
public class App {
    public static boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        Runnable whileFlagFalse = () -> {
            while(!flag) {
            }
            System.out.println("Flag is now TRUE");
        };

        new Thread(whileFlagFalse).start();
        Thread.sleep(1000);
        flag = true;
    }
}
Paling menarik, ini berkemungkinan besar tidak berfungsi. Urutan baharu tidak akan melihat perubahan dalam flagmedan. Untuk membetulkan ini untuk flagmedan, kita perlu menggunakan volatilekata kunci. Bagaimana dan mengapa? Pemproses melakukan semua tindakan. Tetapi hasil pengiraan mesti disimpan di suatu tempat. Untuk ini, terdapat memori utama dan terdapat cache pemproses. Cache pemproses adalah seperti sebahagian kecil memori yang digunakan untuk mengakses data dengan lebih cepat berbanding semasa mengakses memori utama. Tetapi semuanya mempunyai kelemahan: data dalam cache mungkin tidak terkini (seperti dalam contoh di atas, apabila nilai medan bendera tidak dikemas kini). Jadivolatilekata kunci memberitahu JVM bahawa kami tidak mahu cache pembolehubah kami. Ini membolehkan hasil terkini dilihat pada semua urutan. Ini adalah penjelasan yang sangat mudah. Bagi volatilekata kunci, saya amat mengesyorkan anda membaca artikel ini . Untuk maklumat lanjut, saya juga menasihati anda untuk membaca Model Memori Java dan Kata Kunci Meruap Java . Di samping itu, adalah penting untuk diingat bahawa ini volatileadalah mengenai keterlihatan, dan bukan tentang keatoman perubahan. Melihat kod dalam bahagian "Keadaan perlumbaan", kita akan melihat petua alat dalam IntelliJ IDEA: Lebih baik bersama: Java dan kelas Thread.  Bahagian III — Interaksi - 7Pemeriksaan ini telah ditambahkan pada IntelliJ IDEA sebagai sebahagian daripada isu IDEA-61117 , yang telah disenaraikan dalam Nota Keluaran pada tahun 2010.

Atomiti

Operasi atom ialah operasi yang tidak boleh dibahagikan. Sebagai contoh, operasi memberikan nilai kepada pembolehubah mestilah atom. Malangnya, operasi kenaikan bukan atom, kerana kenaikan memerlukan sebanyak tiga operasi CPU: dapatkan nilai lama, tambah satu padanya, kemudian simpan nilai. Mengapakah atomiti penting? Dengan operasi kenaikan, jika terdapat keadaan perlumbaan, maka sumber yang dikongsi (iaitu nilai yang dikongsi) mungkin tiba-tiba berubah pada bila-bila masa. Selain itu, operasi yang melibatkan struktur 64-bit, contohnya longdan double, bukan atom. Butiran lanjut boleh dibaca di sini: Pastikan atomicity semasa membaca dan menulis nilai 64-bit . Masalah yang berkaitan dengan atomicity boleh dilihat dalam contoh ini:
public class App {
    public static int value = 0;
    public static AtomicInteger atomic = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 10000; i++) {
                value++;
                atomic.incrementAndGet();
            }
        };
        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }
        Thread.sleep(300);
        System.out.println(value);
        System.out.println(atomic.get());
    }
}
Kelas khas AtomicIntegerakan sentiasa memberi kita 30,000, tetapi valueakan berubah dari semasa ke semasa. Terdapat gambaran ringkas tentang topik ini: Pengenalan kepada Pembolehubah Atom di Jawa . Algoritma "banding-dan-tukar" terletak di tengah-tengah kelas atom. Anda boleh membaca lebih lanjut mengenainya di sini dalam Perbandingan algoritma bebas kunci - CAS dan FAA pada contoh JDK 7 dan 8 atau dalam artikel Bandingkan-dan-tukar di Wikipedia. Lebih baik bersama: Java dan kelas Thread.  Bahagian III — Interaksi - 9

http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html

Berlaku-sebelum ini

Terdapat konsep menarik dan misteri yang dipanggil "berlaku sebelum". Sebagai sebahagian daripada kajian anda tentang benang, anda harus membaca mengenainya. Hubungan berlaku-sebelum menunjukkan susunan tindakan antara urutan akan dilihat. Terdapat banyak tafsiran dan ulasan. Berikut ialah salah satu pembentangan terbaharu mengenai subjek ini: Java "Happens-Sebelum" Hubungan .

Ringkasan

Dalam ulasan ini, kami telah meneroka beberapa butiran tentang cara urutan berinteraksi. Kami membincangkan masalah yang mungkin timbul, serta cara untuk mengenal pasti dan menghapuskannya. Senarai bahan tambahan mengenai topik: Lebih baik bersama: Java dan kelas Thread. Bahagian I — Benang pelaksanaan Lebih baik bersama: Java dan kelas Benang. Bahagian II — Penyegerakan Lebih baik bersama: Java dan kelas Thread. Bahagian IV — Boleh Dipanggil, Masa Depan dan rakan Lebih baik bersama: Java dan kelas Thread. Bahagian V — Pelaksana, ThreadPool, Fork/Join Better together: Java dan kelas Thread. Bahagian VI - Jauhkan api!
Komen
  • Popular
  • Baru
  • Tua
Anda mesti log masuk untuk meninggalkan ulasan
Halaman ini tidak mempunyai sebarang ulasan lagi