Въведение в нишките на Java

Тази статия, една от първите публикувани някога от JavaWorld, описва как нишките се прилагат в езика за програмиране Java, започвайки с общ преглед на нишките.

Просто казано, нишката е пътят на изпълнение на програмата. Повечето програми, написани днес, се изпълняват като една нишка, причинявайки проблеми, когато трябва да се случват едновременно множество събития или действия. Да кажем, например, че една програма не е способна да рисува картини, докато чете натискания на клавиши. Програмата трябва да обърне цялото си внимание на въвеждането от клавиатурата без възможност за обработка на повече от едно събитие наведнъж. Идеалното решение на този проблем е безпроблемното изпълнение на две или повече секции на програма едновременно. Threads ни позволява да правим това.

Изучаване на Java нишки

Тази статия е част от архива на техническото съдържание на JavaWorld. Вижте следното, за да научите повече за Java нишките и паралелността:

Разбиране на нишките Java ( серия Java 101 , 2002):

  • Част 1: Представяне на нишки и изпълними
  • Част 2: Синхронизация на нишки
  • Част 3: Планиране на нишки и изчакайте / уведомете
  • Част 4: Групи нишки и нестабилност

Свързани статии

  • Java с хипернишки: Използване на API за едновременност на Java (2006)
  • По-добри монитори за многонишкови програми (2007)
  • Разбиране на едновременността на актьора, част 1 (2009)
  • Откриване и обработка на висящи нишки (2011)

Също така проверете картата на сайта JavaWorld и търсачката .

Многопоточните приложения осигуряват мощната си мощност, като изпълняват едновременно много нишки в рамките на една програма. От логическа гледна точка многопоточността означава, че няколко реда на една програма могат да бъдат изпълнени едновременно, но това не е същото като да стартирате програма два пъти и да кажете, че има няколко реда на програма, които се изпълняват едновременно време. В този случай операционната система третира програмите като два отделни и отделни процеса. Под Unix раздвояването на процес създава дъщерен процес с различно адресно пространство както за код, така и за данни. Въпреки това,fork()създава много режийни разходи за операционната система, което я прави много интензивна за процесора операция. Чрез стартиране на нишка вместо това се създава ефективен път на изпълнение, като същевременно се споделя първоначалната област с данни от родителя. Идеята за споделяне на областта с данни е много полезна, но повдига някои проблемни области, които ще обсъдим по-късно.

Създаване на нишки

Създателите на Java любезно са проектирали два начина за създаване на нишки: внедряване на интерфейс и разширяване на клас. Разширяването на клас е начинът, по който Java наследява методи и променливи от родителски клас. В този случай може да се разшири или наследи само от един родителски клас. Това ограничение в Java може да бъде преодоляно чрез внедряване на интерфейси, което е най-често срещаният начин за създаване на нишки. (Обърнете внимание, че актът на наследяване само позволява класът да се изпълнява като нишка. start()Изпълнението зависи от класа и т.н.)

Интерфейсите предоставят начин на програмистите да положат основите на един клас. Те се използват за проектиране на изискванията за набор от класове за изпълнение. Интерфейсът настройва всичко и класът или класовете, които прилагат интерфейса, вършат цялата работа. Различният набор от класове, които реализират интерфейса, трябва да следват същите правила.

Има няколко разлики между клас и интерфейс. Първо, интерфейсът може да съдържа само абстрактни методи и / или статични крайни променливи (константи). Класовете, от друга страна, могат да прилагат методи и да съдържат променливи, които не са константи. Второ, интерфейсът не може да реализира никакви методи. Клас, който реализира интерфейс, трябва да реализира всички методи, дефинирани в този интерфейс. Интерфейсът има способността да се разширява от други интерфейси и (за разлика от класовете) може да се простира от множество интерфейси. Освен това интерфейсът не може да бъде създаден с новия оператор; например Runnable a=new Runnable();не е разрешено.

Първият метод за създаване на нишка е просто да се разшири от Threadкласа. Направете това само ако класът, от който се нуждаете, изпълнен като нишка, никога не трябва да бъде разширяван от друг клас. Най- Threadкласа е дефинирана в java.lang пакет, който трябва да бъде внесен, така че нашите класове са наясно с неговото определение.

import java.lang.*; public class Counter extends Thread { public void run() { .... } }

Горният пример създава нов клас, Counterкойто разширява Threadкласа и замества Thread.run()метода за собствено изпълнение. В run()метода е мястото, където цялата работа на Counterнишката клас се прави. Същият клас може да бъде създаден чрез внедряване на Runnable:

import java.lang.*; public class Counter implements Runnable { Thread T; public void run() { .... } }

Тук абстрактният run()метод е дефиниран в интерфейса Runnable и е внедрен. Имайте предвид, че имаме екземпляр на Threadкласа като променлива на Counterкласа. Единствената разлика между двата метода е, че чрез внедряването на Runnable има по-голяма гъвкавост при създаването на класа Counter. В горния пример все още съществува възможност за разширяване на Counterкласа, ако е необходимо. По-голямата част от създадените класове, които трябва да се изпълняват като нишка, ще изпълняват Runnable, тъй като те вероятно разширяват някои други функции от друг клас.

Не мислете, че интерфейсът Runnable прави някаква реална работа, когато нишката се изпълнява. Това е просто клас, създаден, за да даде представа за дизайна на Threadкласа. Всъщност тя е много малка, съдържаща само един абстрактен метод. Ето дефиницията на интерфейса Runnable директно от източника на Java:

package java.lang; public interface Runnable { public abstract void run(); }

Това е всичко за интерфейса Runnable. Интерфейсът осигурява само дизайн, върху който трябва да се приложат класове. В случай на Runnable интерфейс, той налага дефиницията само на run()метода. Следователно по-голямата част от работата се извършва в Threadкласа. По-внимателният поглед към раздел в дефиницията на Threadкласа ще даде представа какво всъщност се случва:

public class Thread implements Runnable { ... public void run() { if (target != null) { target.run(); } } ... }

От горния кодов фрагмент е очевидно, че класът Thread също така изпълнява интерфейса Runnable. Thread. run()проверява дали целевият клас (класът, който ще се изпълнява като нишка) не е равен на null и след това изпълнява run()метода на целевия. Когато това се случи, run()методът на целта ще се изпълнява като собствена нишка.

Стартиране и спиране

Тъй като различните начини за създаване на екземпляр на нишка вече са очевидни, ще обсъдим изпълнението на нишки, започвайки с наличните начини за стартиране и спиране с помощта на малък аплет, съдържащ нишка, за да илюстрираме механиката:

Пример за CounterThread и изходен код

Горният аплет ще започне да брои от 0, показвайки изхода си както на екрана, така и на конзолата. Един бърз поглед може да създаде впечатлението, че програмата ще започне да брои и ще показва всяко число, но това не е така. По-внимателно проучване на изпълнението на този аплет ще разкрие истинската му идентичност.

In this case, the CounterThread class was forced to implement Runnable since it extended the class Applet. As in all applets, the init() method gets executed first. In init(), the variable Count is initialized to zero and a new instance of the Thread class is created. By passing this to the Thread constructor, the new thread will know which object to run. In this case this is a reference to CounterThread. After the thread is created it needs to be started. The call to start() will call the target's run() method, which is CounterThread.run(). The call to start() will return right away and the thread will start executing at the same time. Note that the run() method is an infinite loop. It is infinite because once the run() method exits, the thread stops executing. The run() method will increment the variable Count, sleep for 10 milliseconds and send a request to refresh the applet's display.

Note that it is important to sleep somewhere in a thread. If not, the thread will consume all CPU time for the process and will not allow any other methods such as threads to be executed. Another way to cease the execution of a thread is to call the stop() method. In this example, the thread stops when the mouse is pressed while the cursor is in the applet. Depending on the speed of the computer the applet runs on, not every number will be displayed, because the incrementing is done independent of the painting of the applet. The applet can not be refreshed at every request, so the OS will queue the requests and successive refresh requests will be satisfied with one refresh. While the refreshes are queuing up, the Count is still being incremented but not displayed.

Suspending and resuming

Once a thread is stopped, it cannot be restarted with the start() command, since stop() will terminate the execution of a thread. Instead you can pause the execution of a thread with the sleep() method. The thread will sleep for a certain period of time and then begin executing when the time limit is reached. But, this is not ideal if the thread needs to be started when a certain event occurs. In this case, the suspend() method allows a thread to temporarily cease executing and the resume() method allows the suspended thread to start again. The following applet shows the above example modified to suspend and resume the applet.

public class CounterThread2 extends Applet implements Runnable { Thread t; int Count; boolean suspended; public boolean mouseDown(Event e,int x, int y) { if(suspended) t.resume(); else t.suspend(); suspended = !suspended; return true; } ... }

CounterThread2 Example and Source code

За да се следи текущото състояние на аплета, се използва булева променлива suspended. Разграничаването на различните състояния на аплета е важно, тъй като някои методи ще хвърлят изключения, ако са извикани, докато са в грешно състояние. Например, ако аплетът е стартиран и спрян, изпълнението на start()метода ще изведе IllegalThreadStateExceptionизключение.