* Wait, notify и notifyAll
Публикувано на 19 октомври 2009 от Филип Петров. Записано в Java.
Вече се запознахме с методът sleep() за нишки в Java, както и възможността да прекъснем „спането“ на нишката чрез метод interrupt(). Използването на метод sleep() всъщност прехвърля текущата нишка в „Not Runnable“ статус за определен период от време и по този начин дава процесорно време на другите нишки. Важно е да се спомене, че ако методът, който е извикал sleep(), е синхронизиран (synchronized), то никой не може да достъпи обектите в него по време на неговия „sleep“ период! Извикването на „interrupt()“ за тази нишка ще прекъсне sleep() преждевременно.
Когато имаме обект (говорим за който и да е обект създаден с оператор new), то разполагаме с нестатичен метод Object.wait(). Този метод на пръв поглед предизвиква същия ефект както Thread.sleep() – прехвърля текущата нишка (тази, която е извикала метода) в „Not Runnable“ статус за определено време. Такъв обект се нарича „заключващ обект“ за нишката. Първата разлика е, че Object.wait() може да бъде извикан само в синхронизиран метод. Втората разлика е, че Object.wait() може да приспи нишката за неопределено време, докато Thread.sleep() е с фиксирано. Освен това ако имаме Thread T, то T.sleep() ще „приспи“ нишката T, докато T.wait() ще приспи текущата нишка (тази, която е извикала метод T.wait()), докато някой друг не извика T.notify().
Преди да дадем пример трябва да кажем, че wait() и notify() са final методи за клас Object. Понеже Object е базов шаблон за клас, който всеки наследява по подразбиране, то всеки един клас в Java ги притежава.
В първият пример ще демонстрираме как можем да накараме една нишка да „заспи“ докато не се извършат необходимите стартирани в нея изчисления:
public class waitNotifyExample{
public static void main(String[]args){
ThreadExample T = new ThreadExample();
T.start();
synchronized(T){
System.out.println("Waiting until calculations finish...");
try{
T.wait();
}
catch (InterruptedException e){}
}
System.out.println("Result: "+T.sum);
}
}
class ThreadExample extends Thread{
public static int sum=0;
public void run(){
synchronized (this){
this.sum();
System.out.println("Finished summing. Now the program can continue...");
this.notify();
}
}
public void sum(){
System.out.println("Suming the first 500 integers...");
for(int i=1; i<=500; i++){
this.sum = this.sum+i;
}
}
}
Преди да бъде извикан „wait()“ методът за обекта, нишката е длъжна да се синхронизира по него (т.е. други методи не могат да го модифицират). След това цялата нишка се „приспива“ в т.нар. „wait list“ (от примера по-горе приспахме главната нишка на main метода). В последствие друга нишка има възможност да „събуди“ обекта чрез метод „notify()“ (това направи самата нишка, по която бяхме приспали main метода).
В този пример показахме как главната нишка (main) извиква друга нишка и изчаква докато тя си свърши работата. Нека погледнем и обратния вариант – главната нишка стартира нова нишка и новата нишка изчаква докато главната не я уведоми:
public class myfirstprogram {
public static void main(String args[]) throws Exception {
System.out.println("Starting thread...");
MyThread T = new MyThread("MyThread");
System.out.println("Main thread printing dots...");
for (int i = 0; i < 50; i++) {
Thread.sleep(50);
System.out.print(".");
}
T.start();
}
}
class MyThread implements Runnable{
boolean ready;
Thread T;
String name;
MyThread(String name){
this.ready = false;
this.name = name;
this.T = new Thread(this, name);
this.T.start();
}
synchronized void suspendThread(){
System.out.println(this.name+" suspended until main finishes...");
while (!ready){
try{
this.wait();
}
catch(InterruptedException e){}
}
System.out.println("\n"+this.name+" is now resumed!");
}
synchronized void start(){
ready = true;
notify();
}
public void run() {
this.suspendThread();
}
}
Нека сега покажем още един пример, в който две нишки се „блокират“ една друга докато изпълнението на съответните методи не приключи. Имаме два класа и две инстанции – обект, който внася пари и обект, който ги прибира. Нека погледнем следният пример:
public class WaitNotifyExample{
public static void main(String args[]) {
MoneyQueue q = new MoneyQueue();
MoneyGiver mg = new MoneyGiver(q, "MoneyGiver");
MoneyFetcher mf = new MoneyFetcher(q, "MoneyFetcher");
}
}
class MoneyQueue{
double moneyTransfer;
boolean haveMoneyInAccount = false;
synchronized double getMoney(String name) {
if(haveMoneyInAccount == false){
try {
this.wait();
}
catch(InterruptedException e) {
System.out.println("This should not happen in this program");
}
}
System.out.println(name +" fetched " + moneyTransfer);
haveMoneyInAccount = false;
this.notify();
return moneyTransfer;
}
synchronized void addMoney(){
if(haveMoneyInAccount == true){
try {
this.wait();
}
catch(InterruptedException e) {
System.out.println("This should not happen in this program");
}
}
System.out.print("Add money: ");
java.util.Scanner s = new java.util.Scanner(System.in);
double moneyTransfer = s.nextDouble();
this.moneyTransfer = moneyTransfer;
haveMoneyInAccount = true;
this.notify();
}
}
class MoneyGiver implements Runnable{
String name;
Thread T;
MoneyQueue q;
public MoneyGiver(MoneyQueue q, String name){
this.q = q;
this.name = name;
T = new Thread(this, "MoneyGiver");
T.start();
}
public void run() {
while(true){
q.addMoney();
}
}
}
class MoneyFetcher implements Runnable{
double money;
String name;
Thread T;
MoneyQueue q;
public MoneyFetcher(MoneyQueue q, String name) {
this.q = q;
this.name = name;
money = 0;
T = new Thread(this, "MoneyFetcher");
T.start();
}
public void run() {
while(true) {
this.money += q.getMoney(this.name);
System.out.println(this.name+" now have "+this.money);
}
}
}
MoneyQueue е клас, в който са дефинирани два синхронизирани метода – addMoney и getMoney. Освен това създаваме два обекта – MoneyGiver (вкарващ пари в системата) и MoneyFetcher (взимащ тези пари и натрупващ ги в собствената си сметка).
Идеята на примера е следната – в началото в MoneyQueue няма никакви пари, което е описано чрез променливата haveMoneyInAccount. Двата обекта MoneyGiver и MoneyFetcher се стартират в две нишки. MoneyFetcher моментално се опитва да вземе пари, но тъй като такива няма, той изпада в спящ режим (в метод getMoney() изпадаме в статус wait(), с което извикващия метод започва да чака). При MoneyGiver ситуацията е точно обратната – виждаме, че няма пари в опашката и затова се поисква въвеждане на такива от клавиатурата. В момента в който вкараме пари в опашката, то haveMoneyInAccount става true и се извиква this.notify(), с което „събуждаме“ другата нишка. MoneyGiver мометално ще изника същия метод (addMoney) отново, но този път ще забележи, че все още има пари за взимане в опашката и затова ще изпадне в wait() статус (докато парите бъдат взети). MoneyFetcher вече е „събуден“ – той ще вземе парите от опашката и ще укаже, че в нея вече няма пари (тоест при следващо извикване на същия метод той самия пак ще е в wait() статус). Естествено отново се извиква this.notify(), за да „събудим“ обекта MoneyGiver. Тази поредност в случая може да се повтаря до безкрайност.
Методът notifyAll() е по-специален – той „събужда“ всички нишки, които са попаднали в wait() статус. Попринцип когато пишете програми трябва много добре да прецените колко нишки евентуално могат да заспят. Ако е само една, то notify() е достатъчен. Ако са много, то е по-вероятно да ви трябва notifyAll(). Ако използвате notify() и повече от една нишка заспи, то рискувате нишките ви да „зациклят“. Въпреки това не препоръчваме тактиката да използвате винаги notifyAll() – ако това ви се налага, но не е очаквано, то по-добре си напишете програмата по-добре.
Обикновено използваме notifyAll() когато нишките имат споделен ресурс по който те се заключват:
public class myfirstprogram {
public static void main(String args[]) throws Exception {
ThreadsResource tr = new ThreadsResource();
MyThread T1 = new MyThread("MyThread1", tr);
MyThread T2 = new MyThread("MyThread2", tr);
for (int i = 0; i < 50; i++){
Thread.sleep(50);
System.out.print(".");
}
System.out.println();
tr.resume();
}
}
class ThreadsResource{
boolean ready = false;
synchronized void suspendThread(){
System.out.print(Thread.currentThread().getName());
System.out.println(" suspended until main finishes...");
while(!ready){
try{
this.wait();
}
catch(InterruptedException e){}
}
System.out.println(Thread.currentThread().getName()+" is now resumed!");
}
synchronized void resume() {
ready = true;
this.notifyAll();
}
}
class MyThread implements Runnable{
ThreadsResource tr;
Thread T;
MyThread(String name, ThreadsResource tr){
this.tr = tr;
this.T = new Thread(this, name);
this.T.start();
}
public void run() {
tr.suspendThread();
}
}
Виждате, че когато споделеният ресурс ThreadsResource извика „notifyAll()“, то всички нишки, които са „заспали“ чрез него се „събуждат“. Това например е често използвана тактика при многопотребителски игри, при които синхронизацията е важна.
Trackback URI | RSS за коментарите
Пусни коментар
Страници
Категории
- C/C++ (45)
- DB (36)
- Dogs (49)
- Food (7)
- History (8)
- Java (33)
- Lada (41)
- Math (104)
- Metodos (23)
- NetSec (36)
- Other (76)
- Politics (32)
- Probability (13)
- VC++.Net (1)
- XHTML/JS (25)
Нови
- Един виц за капитализма
- Как да получиш целувка?
- Лека разходка на Витоша
- Роко и Берра на училище
- Газова бутилка под багажника на Лада Нива