Кіріспе
Multithreading Java-ға алғашқы күндерінен бастап енгізілген. Ендеше, көп ағынның не туралы екенін жылдам қарастырайық.
Бастапқы нүкте ретінде Oracle ресми сабағын алайық: «
Сабақ: «Сәлем әлем!» қолданбасы ». Hello World қолданбасының codeын аздап келесіге өзгертейік:
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args
бағдарлама іске қосылғанда берілген енгізу параметрлерінің массиві болып табылады. Бұл codeты сыныптың аты мен кеңейтіміне сәйкес келетін аты бар файлға сақтайық
.java
.
Javac утorтасын қолданып компиляция жасайық :
javac HelloWorldApp.java
Осыдан кейін codeты қандай да бір параметрмен шақырыңыз, мысалы, Роджер:
java HelloWorldApp Roger
Біздің codeта қазір елеулі ақау бар. Егер біз ешқандай аргумент жібермесек (яғни java HelloWorldApp-ты орындасақ), біз қатені аламыз:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
деп аталатын ағында ерекше жағдай (яғни қате) орын алды
main
. Java-да қандай да бір ағындар бар екен? Міне, біздің саяхатымыз басталады.
Java және ағындар
Жіптің не екенін түсіну үшін Java қолданбасы қалай іске қосылатынын түсіну керек. Кодымызды келесідей өзгертейік:
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
}
}
}
Енді оны javac көмегімен қайтадан құрастырайық. Әрі қарай, ыңғайлы болу үшін біз Java codeын бөлек терезеде іске қосамыз. Windows жүйесінде мұны келесідей орындауға болады:
start java HelloWorldApp
. Енді
jps утorтасын пайдаланып , Java бізге қандай ақпаратты айтатынын көрейік:
Бірінші сан - PID немесе Процесс идентификаторы, процесс идентификаторы. Процесс дегеніміз не?
Процесс — это совокупность codeа и данных, разделяющих общее виртуальное addressное пространство.
Процестердің көмегімен әртүрлі бағдарламалардың орындалуы бір-бірінен оқшауланады: әрбір қосымша басқа бағдарламаларға кедергі жасамай, өзінің жеке жады аймағын пайдаланады. Мен сізге мақаланы толығырақ оқуға кеңес беремін: "
https://habr.com/post/164487/ ". Процесс ағындарсыз өмір сүре алмайды, сондықтан процесс бар болса, онда кем дегенде бір ағын бар. Бұл Java-да қалай болады? Java бағдарламасын іске қосқан кезде оның орындалуы
main
. Біз бағдарламаға кіреміз, сондықтан бұл арнайы әдіс
main
кіру нүктесі немесе «кіру нүктесі» деп аталады. Әдіс әрқашан Java виртуалды машинасы (JVM) бағдарламамызды орындай алатындай
main
болуы керек .
public static void
Қосымша мәліметтер алу үшін «
Java негізгі әдісі неге статикалық? » бөлімін қараңыз. Java іске қосу құралы (java.exe немесе javaw.exe) қарапайым қолданба болып табылады (қарапайым C қолданбасы): ол әртүрлі DLL файлдарын жүктейді, олар шын мәнінде JVM. Java іске қосу құралы Java Native Interface (JNI) қоңырауларының белгілі бір жинағын жасайды. JNI — Java виртуалды машинасының әлемі мен C++ әлемін байланыстыратын механизм. Іске қосу құралы JVM емес, оның жүктеушісі болып шықты. Ол JVM іске қосу үшін орындалатын дұрыс пәрмендерді біледі. JNI қоңыраулары арқылы барлық қажетті ортаны қалай ұйымдастыру керектігін біледі. Қоршаған ортаны бұл ұйымдастыру әдетте деп аталатын негізгі жіпті құруды да қамтиды
main
. Java процесінде қандай ағындар өмір сүретінін нақтырақ көру үшін JDK құрамына кіретін
jvisualvm бағдарламасын қолданамыз. Процестің pid мәнін біле отырып, біз ол туралы деректерді бірден аша аламыз:
jvisualvm --openpid айдипроцесса
Бір қызығы, әрбір ағынның процесс үшін бөлінген жадтағы жеке аймағы бар. Бұл жады құрылымы стек деп аталады. Стек кадрлардан тұрады. Фрейм - бұл әдісті шақыру нүктесі, орындау нүктесі. Жақтауды StackTraceElement ретінде де көрсетуге болады (
StackTraceElement үшін Java API қараңыз ).
Әрбір ағынға бөлінген жад туралы толығырақ мына жерден оқи аласыз .
Егер Java API- ге қарап , Thread сөзін іздесек,
java.lang.Thread класы бар екенін көреміз . Дәл осы класс Java тіліндегі ағынды көрсетеді және біз осымен жұмыс істеуіміз керек.
java.lang.Thread
Java тіліндегі ағын класс данасы ретінде ұсынылған
java.lang.Thread
. Java тіліндегі Thread класының даналарының өзі ағындар емес екенін бірден түсінген жөн. Бұл JVM және операциялық жүйе басқаратын төменгі деңгейлі ағындарға арналған API түрі ғана. JVM-ді java іске қосу құралы арқылы іске қосқан кезде, ол атауы бар негізгі ағынды
main
және тағы бірнеше қызмет ағынын жасайды. Thread класының JavaDoc бағдарламасында айтылғандай:
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
ағындардың 2 түрі бар: демондар және демондар емес. Демон ағындары фондық режимде кейбір жұмыстарды орындайтын фондық ағындар (қызметтік ағындар).
Бұл қызықты термин «Максвеллдің жынына» сілтеме болып табылады, ол туралы толығырақ « жындар » туралы Уикипедия мақаласынан оқи аласыз . Құжаттамада көрсетілгендей, JVM келесіге дейін бағдарламаны (процесті) орындауды жалғастырады:
- Runtime.exit әдісі шақырылмаған
- Барлық демон емес ағындар өз жұмысын аяқтады (қатесіз де, шығарылған ерекшеліктермен де)
Сондықтан маңызды деталь: демон ағындарын орындалатын кез келген пәрменде тоқтатуға болады. Сондықтан олардағы деректердің тұтастығына кепілдік берілмейді. Сондықтан демон ағындары кейбір қызмет тапсырмалары үшін қолайлы. Мысалы, Java-да қоқыс жинағышқа (GC) қатысты соңғы әдістерді немесе ағындарды өңдеуге жауап беретін ағын бар. Әрбір ағын қандай да бір топқа жатады (
ThreadGroup ). Ал топтар қандай да бір иерархияны немесе құрылымды құра отырып, бір-біріне кіре алады.
public static void main(String []args){
Thread currentThread = Thread.currentThread();
ThreadGroup threadGroup = currentThread.getThreadGroup();
System.out.println("Thread: " + currentThread.getName());
System.out.println("Thread Group: " + threadGroup.getName());
System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
Топтар ағындарды басқаруды оңтайландыруға және оларды бақылауға мүмкіндік береді. Топтардан басқа, ағындардың өзіндік ерекшелік өңдегіші бар. Мысал қарастырайық:
public static void main(String []args) {
Thread th = Thread.currentThread();
th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("An error occurred: " + e.getMessage());
}
});
System.out.println(2/0);
}
Нөлге бөлу өңдеуші ұстайтын қатені тудырады. Өңдеушіні өзіңіз көрсетпесеңіз, StdError ішінде қателер сағын көрсететін әдепкі өңдегішті іске асыру жұмыс істейді.
Толығырақ http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/ " шолудан оқи аласыз . Сонымен қатар, жіптің басымдығы бар. Басымдықтар туралы қосымша ақпаратты мына жерден оқи аласыз. мақала «
Көп ағындағы Java ағынының басымдығы ».
Жіп құру
Құжаттамада айтылғандай, бізде ағын жасаудың 2 жолы бар. Біріншісі - мұрагерді құру. Мысалы:
public class HelloWorld{
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello, World!");
}
}
public static void main(String []args){
Thread thread = new MyThread();
thread.start();
}
}
Көріп отырғаныңыздай, тапсырма әдісте іске қосылады
run
, ал ағын әдісте іске қосылады
start
. Оларды шатастырмау керек, өйткені... егер әдісті
run
тікелей іске қоссақ, жаңа ағын басталмайды. Бұл
start
JVM-ден жаңа ағын жасауды сұрайтын әдіс. Thread ұрпағы бар опция нашар, себебі біз сынып иерархиясына Thread қосамыз. Екінші кемшілік – біз «Жауапкершіліктің жалғыз» принципін бұза бастадық, өйткені біздің сынып бір уақытта ағынды басқаруға да, осы ағында орындалуы керек кейбір тапсырмаға да жауапты болады. Қайсысы дұрыс?
run
Жауап біз қайта анықтайтын әдісте :
public void run() {
if (target != null) {
target.run();
}
}
Міне , класс данасын жасау кезінде Thread-ге беруге болатын кейбіреулер
target
.
java.lang.Runnable
Сондықтан біз мұны істей аламыз:
public class HelloWorld{
public static void main(String []args){
Runnable task = new Runnable() {
public void run() {
System.out.println("Hello, World!");
}
};
Thread thread = new Thread(task);
thread.start();
}
}
Ол сонымен қатар
Runnable
Java 1.8 нұсқасынан бері функционалды интерфейс болып табылады. Бұл ағындар үшін тапсырма codeын одан да әдемі жазуға мүмкіндік береді:
public static void main(String []args){
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
Барлығы
Сонымен, бұл оқиғадан ағынның не екенін, олардың қалай бар екенін және олармен қандай негізгі операцияларды орындауға болатынын түсінемін деп үміттенемін.
Келесі бөлімде жіптердің бір-бірімен қалай әрекеттесетінін және олардың өмірлік циклі қандай екенін түсінген жөн. #Вячеслав
GO TO FULL VERSION