JavaRush /Java Blog /Random-TL /Hindi Mo Masisira ang Java gamit ang isang Thread: Part I...

Hindi Mo Masisira ang Java gamit ang isang Thread: Part III - Interaksyon

Nai-publish sa grupo
Isang maikling pangkalahatang-ideya ng mga tampok ng pakikipag-ugnayan sa thread. Noong nakaraan, tiningnan namin kung paano nagsi-synchronize ang mga thread sa isa't isa. Sa pagkakataong ito, susuriin natin ang mga problemang maaaring lumitaw kapag ang mga thread ay nakikipag-ugnayan at pag-uusapan kung paano sila maiiwasan. Magbibigay din kami ng ilang kapaki-pakinabang na link para sa mas malalim na pag-aaral. Hindi mo masisira ang Java gamit ang isang thread: Part III - interaksyon - 1

Panimula

Kaya, alam namin na mayroong mga thread sa Java, na maaari mong basahin sa pagsusuri na " Thread Can't Spoil Java: Part I - Threads " at ang mga thread ay maaaring i-synchronize sa isa't isa, na aming tinalakay sa pagsusuri " Thread Can't Spoil Java ” Spoil: Part II - Synchronization ." Oras na para pag-usapan kung paano nakikipag-ugnayan ang mga thread sa isa't isa. Paano sila nagbabahagi ng mga karaniwang mapagkukunan? Anong mga problema ang maaaring magkaroon nito?

Deadlock

Ang pinakamasamang problema ay Deadlock. Kapag ang dalawa o higit pang mga thread ay naghihintay nang tuluyan para sa isa't isa, ito ay tinatawag na Deadlock. Kumuha tayo ng isang halimbawa mula sa website ng Oracle mula sa paglalarawan ng konsepto 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 has bowed to me!%n",
                    this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s has 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();
    }
}
Ang deadlock dito ay maaaring hindi lumitaw sa unang pagkakataon, ngunit kung ang pagpapatupad ng iyong programa ay natigil, oras na upang tumakbo jvisualvm: Hindi mo masisira ang Java gamit ang isang thread: Part III - interaksyon - 2Kung ang isang plugin ay naka-install sa JVisualVM (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 &lt33a78231> (a Deadlock$Friend) owned by "Thread-0" t@11
Ang thread 1 ay naghihintay ng lock mula sa thread 0. Bakit ito nangyayari? Thread-1nagsisimula sa pagpapatupad at isinasagawa ang pamamaraan Friend#bow. Ito ay minarkahan ng keyword synchronized, iyon ay, kinuha namin ang monitor sa pamamagitan ng this. Sa pasukan sa pamamaraan, nakatanggap kami ng isang link sa isa pa Friend. Ngayon, ang thread Thread-1ay gustong magsagawa ng isang paraan sa isa pa Friend, sa gayon ay nakakakuha din ng lock mula sa kanya. Ngunit kung ang isa pang thread (sa kasong ito Thread-0) ay pinamamahalaang pumasok sa pamamaraan bow, kung gayon ang lock ay abala na at Thread-1naghihintay Thread-0, at kabaliktaran. Ang pagharang ay hindi malulutas, kaya ito ay Patay, iyon ay, patay. Parehong isang death grip (na hindi maaaring pakawalan) at isang patay na bloke mula sa kung saan ang isa ay hindi maaaring makatakas. Sa paksa ng deadlock, maaari mong panoorin ang video: " Deadlock - Concurrency #1 - Advanced Java ".

Livelock

Kung may Deadlock, mayroon bang Livelock? Oo, mayroon) Livelock ay ang mga thread ay tila buhay sa panlabas, ngunit sa parehong oras ay wala silang magagawa, dahil... ang kondisyon kung saan sinusubukan nilang ipagpatuloy ang kanilang trabaho ay hindi matugunan. Sa esensya, ang Livelock ay katulad ng deadlock, ngunit ang mga thread ay hindi "nakabit" sa system na naghihintay para sa monitor, ngunit palaging may ginagawa. 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 ito ay magsisimula muna Thead-1, 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 makikita mula sa halimbawa, ang parehong mga thread ay halili na sinusubukang makuha ang parehong mga kandado, ngunit nabigo sila. Bukod dito, wala sila sa deadlock, iyon ay, biswal ang lahat ay maayos sa kanila at ginagawa nila ang kanilang trabaho. Hindi mo masisira ang Java gamit ang isang thread: Part III - interaksyon - 3Ayon sa JVisualVM, nakikita natin ang mga tagal ng pagtulog at ang panahon ng parke (ito ay kapag ang isang thread ay sumusubok na sakupin ang isang kandado, ito ay napupunta sa estado ng parke, tulad ng napag-usapan natin kanina kapag pinag-uusapan ang pag -synchronize ng thread ). Sa paksa ng livelock, makikita mo ang isang halimbawa: " Java - Thread Livelock ".

Pagkagutom

Bilang karagdagan sa pagharang (deadlock at livelock), may isa pang problema kapag nagtatrabaho sa multithreading - Pagkagutom, o "gutom". Ang hindi pangkaraniwang bagay na ito ay naiiba sa pagharang dahil ang mga thread ay hindi naka-block, ngunit wala silang sapat na mapagkukunan para sa lahat. Samakatuwid, habang ang ilang mga thread ay tumatagal sa lahat ng oras ng pagpapatupad, ang iba ay hindi maaaring isagawa: Hindi mo masisira ang Java gamit ang isang thread: Part III - interaksyon - 4

https://www.logicbig.com/

Ang isang sobrang halimbawa ay matatagpuan dito: " Java - Thread Starvation and Fairness ". Ipinapakita ng halimbawang ito kung paano gumagana ang mga thread sa Starvation at kung paano maaaring ipamahagi ng isang maliit na pagbabago mula sa Thread.sleep hanggang Thread.wait ang load nang pantay-pantay. Hindi mo masisira ang Java gamit ang isang thread: Part III - interaksyon - 5

Kondisyon ng Lahi

Kapag nagtatrabaho sa multithreading, mayroong isang bagay bilang isang "kondisyon ng lahi". Ang hindi pangkaraniwang bagay na ito ay nakasalalay sa katotohanan na ang mga thread ay nagbabahagi ng isang tiyak na mapagkukunan sa kanilang mga sarili at ang code ay nakasulat sa paraang hindi ito nagbibigay ng tamang operasyon sa kasong ito. Tingnan natin 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. At maaaring ganito ang hitsura nito:
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)
Tulad ng makikita mo, habang ito ay itinalaga, newValuemay nangyaring mali at newValuemarami pa. Ang ilan sa mga thread sa kondisyon ng lahi ay nagawang magbago valuesa pagitan ng dalawang koponan na ito. Tulad ng nakikita natin, lumitaw ang isang karera sa pagitan ng mga thread. Ngayon isipin kung gaano kahalaga na hindi gumawa ng mga katulad na pagkakamali sa mga transaksyon sa pera... Makikita rin dito ang mga halimbawa at diagram: “ Code to simulate race condition in Java thread ”.

pabagu-bago ng isip

Sa pagsasalita tungkol sa pakikipag-ugnayan ng mga thread, ito ay nagkakahalaga lalo na tandaan ang keyword volatile. 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-kagiliw-giliw na bagay ay na may mataas na antas ng posibilidad na hindi ito gagana. Hindi makikita ng bagong thread ang pagbabago flag. Upang ayusin ito, flagkailangan mong tumukoy ng keyword para sa field volatile. Paano at bakit? Ang lahat ng mga aksyon ay ginagawa ng processor. Ngunit ang mga resulta ng pagkalkula ay kailangang maimbak sa isang lugar. Para sa layuning ito, mayroong pangunahing memorya at isang cache ng hardware sa processor. Ang mga cache ng processor na ito ay parang isang maliit na piraso ng memorya para sa pag-access ng data nang mas mabilis kaysa sa pag-access sa pangunahing memorya. Ngunit ang lahat ay mayroon ding downside: ang data sa cache ay maaaring hindi kasalukuyan (tulad ng sa halimbawa sa itaas, kapag ang halaga ng flag ay hindi na-update). Kaya, volatilesinasabi ng keyword sa JVM na hindi namin gustong i-cache ang aming variable. Binibigyang-daan ka nitong makita ang aktwal na resulta sa lahat ng mga thread. Ito ay isang napaka-pinasimpleng pagbabalangkas. Sa paksang ito, volatilelubos na inirerekomendang basahin ang pagsasalin ng " JSR 133 (Java Memory Model) FAQ ". Ipinapayo ko rin sa iyo na magbasa nang higit pa tungkol sa mga materyal na " Modelo ng Java Memory " at " Java Volatile Keyword ". Bilang karagdagan, mahalagang tandaan na volatileito ay tungkol sa visibility, at hindi tungkol sa atomicity ng mga pagbabago. Kung kukunin natin ang code mula sa "Kondisyon ng Lahi", makakakita tayo ng pahiwatig sa IntelliJ Idea: Hindi mo masisira ang Java gamit ang isang thread: Part III - interaksyon - 6Idinagdag ang inspeksyon (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 operasyon ng pagtatalaga ng halaga sa isang variable ay atomic. Sa kasamaang palad, ang pagtaas ay hindi isang atomic na operasyon, dahil ang isang pagtaas ay nangangailangan ng hanggang tatlong operasyon: kunin ang lumang halaga, magdagdag ng isa dito, at i-save ang halaga. Bakit mahalaga ang atomicity? Sa halimbawa ng pagtaas, kung mangyari ang isang kundisyon ng lahi, sa anumang oras ang nakabahaging mapagkukunan (ibig sabihin, ang nakabahaging halaga) ay maaaring biglang magbago. Bilang karagdagan, mahalaga na ang mga 64-bit na istruktura ay hindi rin atomic, halimbawa longat double. Maaari kang magbasa ng higit pa dito: " Tiyakin ang atomicity kapag nagbabasa at nagsusulat ng mga 64-bit na halaga ". Ang isang halimbawa ng mga problema sa atomicity ay makikita sa sumusunod na halimbawa:
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 isang espesyal na klase para sa pagtatrabaho sa atomic Integeray palaging magpapakita sa amin ng 30000, ngunit valueito ay magbabago paminsan-minsan. Mayroong maikling pangkalahatang-ideya sa paksang ito " Isang Panimula sa Atomic Variables sa Java ". Ang Atomic ay batay sa Compare-and-Swap algorithm. Maaari mong basahin ang higit pa tungkol dito sa artikulo sa Habré " Paghahambing ng mga algorithm na walang Lock - CAS at FAA gamit ang halimbawa ng JDK 7 at 8 " o sa Wikipedia sa artikulo tungkol sa " Paghahambing sa palitan ". Hindi mo masisira ang Java gamit ang isang thread: Part III - interaksyon - 8

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

Nangyayari Bago

Mayroong isang kawili-wili at mahiwagang bagay - Nangyayari Bago. Sa pagsasalita tungkol sa mga daloy, sulit din itong basahin. Ipinapahiwatig ng The Happens Before relation ang pagkakasunud-sunod kung saan makikita ang mga aksyon sa pagitan ng mga thread. Maraming interpretasyon at interpretasyon. Ang isa sa mga pinakabagong ulat sa paksang ito ay ang ulat na ito:
Malamang na mas mabuti na ang video na ito ay walang sinasabi tungkol dito. Kaya mag-iiwan na lang ako ng link sa video. Mababasa mo ang " Java - Understanding Happens-before relationships ".

Mga resulta

Sa pagsusuring ito, tiningnan namin ang mga tampok ng pakikipag-ugnayan sa thread. Tinalakay namin ang mga problemang maaaring lumitaw at mga paraan upang matukoy at maalis ang mga ito. Listahan ng mga karagdagang materyales sa paksa: #Viacheslav
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION