CodeGym/Java Blog/Random/Mas mahusay na magkasama: Java at ang klase ng Thread. Ba...
John Squirrels
Antas
San Francisco

Mas mahusay na magkasama: Java at ang klase ng Thread. Bahagi III - Pakikipag-ugnayan

Nai-publish sa grupo
Isang maikling pangkalahatang-ideya ng mga detalye kung paano nakikipag-ugnayan ang mga thread. Noong nakaraan, tiningnan namin kung paano naka-synchronize ang mga thread sa isa't isa. Sa pagkakataong ito, susuriin natin ang mga problemang maaaring lumitaw habang nakikipag-ugnayan ang mga thread, at pag-uusapan natin kung paano maiiwasan ang mga ito. Magbibigay din kami ng ilang kapaki-pakinabang na link para sa mas malalim na pag-aaral. Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi III — Pakikipag-ugnayan - 1

Panimula

Kaya, alam namin na ang Java ay may mga thread. Mababasa mo iyon sa pagsusuri na pinamagatang Better together: Java and the Thread class. Bahagi I — Mga Thread ng pagpapatupad . At na-explore namin ang katotohanang maaaring mag-synchronize ang mga thread sa isa't isa sa review na pinamagatang Better together: Java and the Thread class. Bahagi II — Pag-synchronize . Oras na para pag-usapan kung paano nakikipag-ugnayan ang mga thread sa isa't isa. Paano sila nagbabahagi ng ibinahaging mapagkukunan? Anong mga problema ang maaaring lumitaw dito? Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi III — Pakikipag-ugnayan - 2

Deadlock

Ang pinakanakakatakot na problema sa lahat ay deadlock. Ang deadlock ay kapag ang dalawa o higit pang mga thread ay walang hanggang naghihintay para sa isa pa. Kukuha kami ng halimbawa mula sa Oracle webpage na naglalarawan ng deadlock :
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();
    }
}
Maaaring hindi maganap dito ang deadlock sa unang pagkakataon, ngunit kung nag-hang ang iyong program, oras na para tumakbo jvisualvm: Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi III — Pakikipag-ugnayan - 3Sa naka-install na JVisualVM plugin (sa pamamagitan ng Tools -> Plugins), makikita natin kung saan naganap ang deadlock:
"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
Naghihintay ang thread 1 para sa lock mula sa thread 0. Bakit nangyayari iyon? Thread-1magsisimulang tumakbo at isagawa ang Friend#bowpamamaraan. Ito ay minarkahan ng synchronizedkeyword, na nangangahulugang kinukuha namin ang monitor para sa this(kasalukuyang bagay). Ang input ng pamamaraan ay isang sanggunian sa iba pang Friendbagay. Ngayon, Thread-1gustong isagawa ang pamamaraan sa kabilang Friend, at dapat makuha ang lock nito upang magawa ito. Ngunit kung ang ibang thread (sa kasong ito Thread-0) ay pinamamahalaang pumasok sa bow()pamamaraan, kung gayon ang lock ay nakuha na at Thread-1naghihintay para saThread-0, at kabaliktaran. Ito ay hindi malulutas, at tinatawag namin itong deadlock. Parang death grip na hindi mabitawan, ang deadlock ay mutual blocking na hindi masisira. Para sa isa pang paliwanag ng deadlock, maaari mong panoorin ang video na ito: Deadlock and Livelock Explained .

Livelock

Kung may deadlock, may livelock din ba? Oo, mayroon :) Nangyayari ang livelock kapag ang mga thread sa panlabas na anyo ay tila buhay, ngunit wala silang magagawa, dahil ang (mga) kundisyon na kinakailangan para sa kanila upang ipagpatuloy ang kanilang trabaho ay hindi maaaring matupad. Karaniwan, ang livelock ay katulad ng deadlock, ngunit ang mga thread ay hindi "nakabit" na naghihintay para sa isang monitor. Sa halip, magpakailanman silang gumagawa ng isang bagay. Halimbawa:
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();
    }
}
Ang tagumpay ng code na ito ay nakasalalay sa pagkakasunud-sunod kung saan sinisimulan ng Java thread scheduler ang mga thread. Kung Thead-1magsisimula muna, pagkatapos ay makakakuha tayo ng livelock:
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
...
Tulad ng nakikita mo mula sa halimbawa, sinusubukan ng parehong mga thread na kunin ang parehong mga kandado, ngunit nabigo sila. Ngunit, hindi sila deadlock. Sa panlabas, maayos ang lahat at ginagawa nila ang kanilang trabaho. Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi III — Pakikipag-ugnayan - 4Ayon sa JVisualVM, nakikita natin ang mga panahon ng pagtulog at isang panahon ng parke (ito ay kapag ang isang thread ay sumusubok na kumuha ng lock — pumapasok ito sa estado ng parke, tulad ng napag-usapan natin kanina nang pag-usapan natin ang tungkol sa pag-synchronize ng thread ) . Makakakita ka ng halimbawa ng livelock dito: Java - Thread Livelock .

Pagkagutom

Bilang karagdagan sa deadlock at livelock, may isa pang problema na maaaring mangyari sa panahon ng multithreading: gutom. Ang hindi pangkaraniwang bagay na ito ay naiiba sa mga naunang paraan ng pagharang dahil ang mga thread ay hindi naka-block — wala silang sapat na mapagkukunan. Bilang resulta, habang ang ilang mga thread ay tumatagal ng lahat ng oras ng pagpapatupad, ang iba ay hindi maaaring tumakbo: Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi III — Pakikipag-ugnayan - 5

https://www.logicbig.com/

Makakakita ka ng sobrang halimbawa dito: Java - Thread Starvation and Fairness . Ipinapakita ng halimbawang ito kung ano ang nangyayari sa mga thread sa panahon ng gutom at kung paano hinahayaan ka ng isang maliit na pagbabago mula sa Thread.sleep()upang maipamahagi ang load nang pantay-pantay.Thread.wait()Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi III — Pakikipag-ugnayan - 6

Mga kondisyon ng lahi

Sa multithreading, mayroong isang bagay bilang isang "kondisyon ng lahi". Ang hindi pangkaraniwang bagay na ito ay nangyayari kapag ang mga thread ay nagbabahagi ng isang mapagkukunan, ngunit ang code ay isinulat sa paraang hindi nito tinitiyak ang tamang pagbabahagi. Tingnan ang isang halimbawa:
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();
    }
}
Maaaring hindi makabuo ng error ang code na ito sa unang pagkakataon. Kapag nangyari ito, maaaring ganito ang hitsura:
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)
Gaya ng nakikita mo, may nangyaring mali habang newValueitinalaga ang isang halaga. newValueay masyadong malaki. Dahil sa kondisyon ng lahi, nagawa ng isa sa mga thread na baguhin ang variable valuesa pagitan ng dalawang pahayag. May lahi pala sa pagitan ng mga thread. Ngayon isipin kung gaano kahalaga ang hindi gumawa ng mga katulad na pagkakamali sa mga transaksyon sa pananalapi... Makikita rin dito ang mga halimbawa at diagram: Code para gayahin ang kundisyon ng lahi sa Java thread .

pabagu-bago ng isip

Sa pagsasalita tungkol sa pakikipag-ugnayan ng mga thread, ang volatilekeyword ay nagkakahalaga ng pagbanggit. Tingnan natin ang isang simpleng halimbawa:
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;
    }
}
Ang pinaka-kawili-wili, ito ay malamang na hindi gagana. Hindi makikita ng bagong thread ang pagbabago sa flagfield. Upang ayusin ito para sa flagfield, kailangan nating gamitin ang volatilekeyword. Paano at bakit? Ginagawa ng processor ang lahat ng mga aksyon. Ngunit ang mga resulta ng mga kalkulasyon ay dapat na naka-imbak sa isang lugar. Para dito, mayroong pangunahing memorya at mayroong cache ng processor. Ang mga cache ng processor ay parang isang maliit na tipak ng memorya na ginagamit upang ma-access ang data nang mas mabilis kaysa sa pag-access sa pangunahing memorya. Ngunit lahat ng bagay ay may downside: ang data sa cache ay maaaring hindi up-to-date (tulad ng sa halimbawa sa itaas, kapag ang halaga ng flag field ay hindi na-update). Kaya angvolatileSinasabi ng keyword sa JVM na hindi namin gustong i-cache ang aming variable. Nagbibigay-daan ito sa napapanahon na resulta na makita sa lahat ng mga thread. Ito ay isang napakasimpleng paliwanag. Tulad ng para sa volatilekeyword, lubos kong inirerekumenda na basahin mo ang artikulong ito . Para sa karagdagang impormasyon, ipinapayo ko rin sa iyo na basahin ang Java Memory Model at Java Volatile Keyword . Bukod pa rito, mahalagang tandaan na iyon volatileay tungkol sa visibility, at hindi tungkol sa atomicity ng mga pagbabago. Sa pagtingin sa code sa seksyong "Mga kundisyon ng lahi," makakakita tayo ng tooltip sa IntelliJ IDEA: Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi III — Pakikipag-ugnayan - 7Idinagdag ang inspeksyon na ito sa IntelliJ IDEA bilang bahagi ng isyu na IDEA-61117 , na nakalista sa Mga Tala sa Paglabas noong 2010.

Atomicity

Ang mga operasyon ng atom ay mga operasyon na hindi maaaring hatiin. Halimbawa, ang pagpapatakbo ng pagtatalaga ng isang halaga sa isang variable ay dapat na atomic. Sa kasamaang palad, ang increment na operasyon ay hindi atomic, dahil ang increment ay nangangailangan ng hanggang tatlong operasyon ng CPU: kunin ang lumang halaga, magdagdag ng isa dito, pagkatapos ay i-save ang halaga. Bakit mahalaga ang atomicity? Sa pagpapatakbo ng pagtaas, kung mayroong kundisyon ng lahi, maaaring biglang magbago ang nakabahaging mapagkukunan (ibig sabihin, ang nakabahaging halaga) anumang oras. Bukod pa rito, ang mga operasyong kinasasangkutan ng 64-bit na istruktura, halimbawa longat double, ay hindi atomic. Higit pang mga detalye ang mababasa dito: Tiyaking atomicity kapag nagbabasa at nagsusulat ng mga 64-bit na halaga . Ang mga problemang nauugnay sa atomicity ay makikita sa halimbawang ito:
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());
    }
}
Ang espesyal AtomicIntegerna klase ay palaging magbibigay sa amin ng 30,000, ngunit ang valueay magbabago paminsan-minsan. Mayroong maikling pangkalahatang-ideya ng paksang ito: Panimula sa Atomic Variables sa Java . Ang algorithm na "compare-and-swap" ay nasa gitna ng mga atomic class. Maaari kang magbasa ng higit pa tungkol dito sa Paghahambing ng mga algorithm na walang lock - CAS at FAA sa halimbawa ng JDK 7 at 8 o sa artikulong Maghambing-at-magpalit sa Wikipedia. Mas mahusay na magkasama: Java at ang klase ng Thread.  Bahagi III — Pakikipag-ugnayan - 9

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

Nangyayari-bago

Mayroong isang kawili-wili at mahiwagang konsepto na tinatawag na "nangyayari noon". Bilang bahagi ng iyong pag-aaral ng mga thread, dapat mong basahin ang tungkol dito. Ipinapakita ng ugnayang nangyari-bago ang pagkakasunud-sunod kung saan makikita ang mga pagkilos sa pagitan ng mga thread. Maraming interpretasyon at komentaryo. Narito ang isa sa mga pinakabagong presentasyon sa paksang ito: Java "Happens-Before" Relationships .

Buod

Sa pagsusuring ito, na-explore namin ang ilan sa mga detalye kung paano nakikipag-ugnayan ang mga thread. Tinalakay namin ang mga problemang maaaring lumitaw, pati na rin ang mga paraan upang matukoy at maalis ang mga ito. Listahan ng mga karagdagang materyales sa paksa: Mas mahusay na magkasama: Java at ang klase ng Thread. Bahagi I — Mga Thread ng execution Mas mahusay na magkasama: Java at ang Thread class. Bahagi II — Pag-synchronize Mas mahusay na magkasama: Java at ang Thread na klase. Bahagi IV — Matatawagan, Hinaharap, at mga kaibigan Mas mahusay na magkasama: Java at ang Thread na klase. Part V — Executor, ThreadPool, Fork/Join Better together: Java at ang Thread class. Bahagi VI - Sunog!
Mga komento
  • Sikat
  • Bago
  • Luma
Dapat kang naka-sign in upang mag-iwan ng komento
Wala pang komento ang page na ito