JavaRush /Java блогу /Random-KY /Сиз Java-ны жип менен буза албайсыз: III-бөлүк - Өз ара а...
Viacheslav
Деңгээл

Сиз Java-ны жип менен буза албайсыз: III-бөлүк - Өз ара аракеттенүү

Группада жарыяланган
Жиптин өз ара аракеттенүү өзгөчөлүктөрүнө кыскача сереп. Мурда биз жиптер бири-бири менен синхрондоштурууну карап чыктык. Бул жолу биз жиптер өз ара аракеттенгенде пайда болушу мүмкүн болгон көйгөйлөргө токтолуп, аларды кантип болтурбоо керектиги жөнүндө сүйлөшөбүз. Биз дагы тереңирээк изилдөө үчүн пайдалуу шилтемелерди беребиз. Javaны жип менен буза албайсыз: III бөлүк - өз ара аракеттенүү - 1

Киришүү

Ошентип, биз Java'да жиптер бар экенин билебиз, алар жөнүндө " Java-ны буза алbyte: I бөлүк - Жиптер " жана жиптер бири-бири менен синхрондоштурууга болот, биз аларды карап чыгууда карап чыктык " Thread Can't Spoil Java ” Spoil: II Бөлүм - Синхрондоштуруу ." Жиптер бири-бири менен кандай байланышта экени жөнүндө сүйлөшүүгө убакыт келди. Алар жалпы ресурстарды кантип бөлүшөт? Бул менен кандай көйгөйлөр болушу мүмкүн?

Туюк

Эң жаман көйгөй - бул туюк. Эки же андан көп жип бири-бирин түбөлүк күткөндө, бул туюк деп аталат. Келгиле, Oracle веб-сайтынан " Туйрук " концепциясын сүрөттөөдөн мисал алалы :
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();
    }
}
Бул жерде туюк биринчи жолу пайда болбошу мүмкүн, бирок программаңыздын аткарылышы токтоп калса, анда иштөөгө убакыт келди jvisualvm: Javaны жип менен буза албайсыз: III бөлүк - өз ара аракеттенүү - 2JVisualVMде плагин орнотулган болсо (Куралдар -> Плагиндер аркылуу), биз туюктун кайсы жерде болгонун көрө алабыз:
"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
Жип 1 0 жиптен кулпуну күтүп жатат. Эмне үчүн мындай болуп жатат? Thread-1аткарууну баштайт жана методду ишке ашырат Friend#bow. Ал ачкыч сөз менен белгиленет synchronized, башкача айтканда, биз мониторду this. Методдун кире беришинде биз башкасына шилтеме алдык Friend. Эми жип Thread-1башка методду ишке ашыргысы келет Friend, ошону менен андан кулпуну да алат. Бирок, эгерде башка жип (бул учурда Thread-0) ыкмага кире алган болсо bow, анда кулпу мурунтан эле бош эмес жана Thread-1күтүп турат Thread-0жана тескерисинче. Бөгөттөө чечилбейт, ошондуктан ал Өлгөн, башкача айтканда, өлгөн. Өлүм кармагычы да (бошотууга болбойт) жана адам качып кутула албай турган өлүк блок. Туюк темада сиз видеону көрө аласыз: " Туюктук - Кошумча №1 - Өркүндөтүлгөн Java ".

Livelock

Туюк болсо, анда Түйшүк барбы? Ооба, бар) Livelock бул жиптер сыртынан жандуу көрүнөт, бирок ошол эле учурда алар эч нерсе кыла алышпайт, анткени... оларыц ишини довам этдирмэге чалышян шертлери ерине етирип билмез. Түпкүлүгүндө, Livelock туюкка окшош, бирок жиптер мониторду күтүп жаткан системада “orп” калbyte, бирок ар дайым бир нерсе кылып жатышат. Мисалы:
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();
    }
}
Бул codeдун ийгorги Java жип пландоочусу жиптерди баштоо тартибинен көз каранды. Эгер биринчи башталса Thead-1, биз 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
...
Мисалдан көрүнүп тургандай, эки жип тең кезектешип эки кулпуну тең кармоого аракет кылат, бирок алар ийгorксиз болот. Анын үстүнө алар туңгуюкта эмес, башкача айтканда, визуалдык жактан аларда баары жакшы жана алар өз ишин аткарып жатышат. Javaны жип менен буза албайсыз: III бөлүк - өз ара аракеттенүү - 3JVisualVM ылайык, биз уйку мезгилдерин жана парк мезгorн көрөбүз (бул жип кулпуну ээлөөгө аракет кылганда, ал сейилдөө абалына өтөт, биз жипти синхрондоштуруу жөнүндө мурда талкуулаганбыз ). Livelock темасында сиз мисалды көрө аласыз: " Java - Thread Livelock ".

Ачкачылык

Бөгөттөөдөн тышкары (туюк жана тирүү блок) көп агым менен иштөөдө дагы бир көйгөй бар - ачкачылык же "ачкалык". Бул көрүнүш бөгөттөөдөн жиптер бөгөттөлбөгөндүгү менен айырмаланат, бирок алар жөн гана бардыгына жетиштүү ресурстарга ээ эмес. Ошондуктан, кээ бир жиптер бүт аткаруу убактысын алат, ал эми башкалары аткарылbyte: Javaны жип менен буза албайсыз: III бөлүк - өз ара аракеттенүү - 4

https://www.logicbig.com/

Супер мисалды бул жерден тапса болот: " Java - Thread Starvation and Fairness ". Бул мисалда жиптер Ачарчылыкта кантип иштээрин жана Thread.sleepтен Thread.waitке бир кичинекей өзгөртүү кантип жүктү бирдей бөлүштүрө аларын көрсөтөт. Javaны жип менен буза албайсыз: III бөлүк - өз ара аракеттенүү - 5

Жарыш абалы

Multithreading менен иштөөдө "жарыш шарты" деген нерсе бар. Бул көрүнүш жиптер белгилүү бир ресурсту өз ара бөлүшүүдө жана code бул учурда туура иштөөнү камсыз кылбагандай кылып жазылганында. Келгиле, бир мисал карап көрөлү:
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();
    }
}
Бул code биринчи жолу ката жаратпашы мүмкүн. Жана мындай көрүнүшү мүмкүн:
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)
Көрүнүп тургандай, ал дайындалып жатканда, newValueбир нерсе туура эмес болуп, newValueдагы көп болду. Жарыш абалындагы жиптердин айрымдары valueбул эки команданын ортосунда өзгөрүүгө жетишти. Көрүнүп тургандай, жиптердин ортосунда жарыш пайда болду. Эми акча транзакцияларында ушундай каталарды кетирбөө канчалык маанилүү экенин элестетип көрүңүз... Мисалдарды жана диаграммаларды бул жерден тапса болот: “ Java жипинде жарыш абалын симуляциялоо үчүн code ”.

Учуучу

Жиптердин өз ара аракеттенүүсү жөнүндө сөз кылып жатып, өзгөчө ачкыч сөздү белгилей кетүү керек volatile. Келгиле, жөнөкөй мисалды карап көрөлү:
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;
    }
}
Эң кызыгы, жогорку ыктымалдуулук менен ал иштебей калат. Жаңы жип өзгөрүүнү көрбөйт flag. Муну оңдоо үчүн, талаа үчүн flagачкыч сөздү көрсөтүшүңүз керек volatile. Кантип жана эмнеге? Бардык аракеттер процессор тарабынан аткарылат. Бирок эсептөө натыйжалары бир жерде сакталышы керек. Бул үчүн процессордо негизги эс жана аппараттык кэш бар. Бул процессордун кэштери негизги эстутумга караганда тезирээк маалыматтарга жетүү үчүн эстутумдун кичинекей бөлүгү сыяктуу. Бирок бардыгынын терс жагы да бар: кэштеги маалыматтар учурдагы болбошу мүмкүн (жогорку мисалдагыдай, желектин мааниси жаңыртылган эмес). Ошентип, ачкыч сөз volatileJVMге биз өзгөрмөбүздү кэштегибиз келбейт деп айтат. Бул бардык жиптерде чыныгы натыйжаны көрүүгө мүмкүндүк берет. Бул абдан жөнөкөйлөштүрүлгөн формула болуп саналат. Бул темада " JSR 133 (Java Memory Model) FAQvolatile " котормосун окуу сунушталат . Мен ошондой эле “ Java Memory Model ” жана “ Java Volatile Keyword ” материалдары жөнүндө көбүрөөк окууну сунуштайм . Кошумчалай кетсек, бул өзгөрүүлөрдүн атомдуулугу жөнүндө эмес, көрүнүү жөнүндө экенин эстен чыгарбоо керек . Эгер биз "Жарыш шартынан" codeду алсак, IntelliJ Ideaдан кыйытты көрөбүз: Бул текшерүү (Текшерүү) IntelliJ Ideaга IDEA-61117 чыгарылышынын бир бөлүгү катары кошулган , ал 2010-жылы Release Notes тизмесинде көрсөтүлгөн .volatileJavaны жип менен буза албайсыз: III бөлүк - өз ара аракеттенүү - 6

Атомдук

Атомдук операциялар бөлүүгө болбой турган операциялар. Мисалы, өзгөрмөгө маани берүү операциясы атомдук. Тилекке каршы, көбөйтүү атомдук операция эмес, анткени өсүү үч операцияны талап кылат: эски маанини алуу, ага бирди кошуу жана маанини сактоо. Эмне үчүн атомдук маанилүү? Көбөйтүү мисалында, эгерде жарыш шарты пайда болсо, каалаган убакта бөлүшүлгөн ресурс (б.а., жалпы маани) күтүлбөгөн жерден өзгөрүшү мүмкүн. Мындан тышкары, 64-бит структуралар да атомдук эмес, маанилүү, мисалы longжана double. Көбүрөөк бул жерден окуй аласыз: " 64 биттик маанилерди окуп жана жазганда атомдуулукту камсыз кылыңыз ". Атомдук көйгөйлөрдүн мисалын төмөнкү мисалдан көрүүгө болот:
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());
    }
}
Атом менен иштөө үчүн атайын класс Integerбизге дайыма 30000 көрсөтөт, бирок valueал мезгил-мезгor менен өзгөрүп турат. Бул тема боюнча кыскача сереп бар " Javaдагы атомдук өзгөрмөлөргө киришүү ". Atomic Салыштыруу жана алмаштыруу алгоритмине негизделген. Бул тууралуу Habré макаласында " Блоксуз алгоритмдерди салыштыруу - CAS жана FAA JDK 7 жана 8 мисалында " же Wikipediaдагы " Алмашуу менен салыштыруу " жөнүндө макаладан окуй аласыз. Javaны жип менен буза албайсыз: III бөлүк - өз ара аракеттенүү - 8

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

Буга чейин болот

Кызыктуу жана сырдуу нерсе бар - Мурда болот. Агымдар жөнүндө сөз кыла турган болсок, ал жөнүндө окуу да арзырлык. Мурда болгон мамилеси жиптердин ортосундагы аракеттер көрүнө турган тартипти көрсөтөт. Көптөгөн чечмелөөлөр жана чечмелөөлөр бар. Бул тема боюнча акыркы отчеттордун бири бул отчет болуп саналат:
Бул видеодо бул тууралуу эч нерсе айтылбаганы жакшы. Андыктан мен жөн гана видеого шилтеме калтырам. Сиз окуй аласыз " Java - түшүнүү-байланыштарга чейин ".

Жыйынтыктар

Бул кароодо биз жип менен өз ара аракеттенүүнүн өзгөчөлүктөрүн карадык. Биз пайда болушу мүмкүн болгон көйгөйлөрдү жана аларды аныктоо жана жоюунун жолдорун талкууладык. Тема боюнча кошумча материалдардын тизмеси: #Вячеслав
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION