Panimula
Ang mga thread ay isang kawili-wiling bagay. Sa mga nakaraang pagsusuri, tiningnan namin ang ilan sa mga magagamit na tool para sa pagpapatupad ng multithreading. Tingnan natin kung ano ang iba pang mga kawili-wiling bagay na maaari nating gawin. Sa puntong ito, marami tayong alam. Halimbawa, mula sa "
Better together: Java at ang Thread class. Part I — Threads of execution ", alam namin na ang Thread class ay kumakatawan sa isang thread ng execution. Alam namin na ang isang thread ay gumaganap ng ilang gawain. Kung gusto nating magawa ng ating mga gawain
run
, dapat nating markahan ang thread ng
Runnable
.
Upang matandaan, maaari naming gamitin ang
Tutorialspoint Online Java Compiler :
public static void main(String[] args){
Runnable task = () -> {
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
};
Thread thread = new Thread(task);
thread.start();
}
Alam din natin na mayroon tayong tinatawag na lock. Nalaman namin ang tungkol dito sa "
Better together: Java and the Thread class. Part II — Synchronization . Kung ang isang thread ay nakakuha ng lock, ang isa pang thread na sinusubukang makuha ang lock ay mapipilitang maghintay para sa lock na ilabas:
import java.util.concurrent.locks.*;
public class HelloWorld{
public static void main(String []args){
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
lock.unlock();
};
Thread thread = new Thread(task);
thread.start();
}
}
Sa tingin ko, oras na para pag-usapan kung ano ang iba pang mga kawili-wiling bagay na maaari nating gawin.
Mga semaphore
Ang pinakasimpleng paraan upang makontrol kung gaano karaming mga thread ang maaaring tumakbo nang sabay-sabay ay isang semaphore. Parang signal ng tren. Ang ibig sabihin ng berde ay magpatuloy. Ang ibig sabihin ng pula ay maghintay. Maghintay para sa kung ano mula sa semaphore? Access. Upang makakuha ng access, dapat nating makuha ito. At kapag hindi na kailangan ng access, dapat natin itong ibigay o ilabas. Tingnan natin kung paano ito gumagana. Kailangan nating i-import ang
java.util.concurrent.Semaphore
klase. Halimbawa:
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
Runnable task = () -> {
try {
semaphore.acquire();
System.out.println("Finished");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
Thread.sleep(5000);
semaphore.release(1);
}
Gaya ng nakikita mo, ang mga operasyong ito (pagkuha at pagpapalabas) ay nakakatulong sa amin na maunawaan kung paano gumagana ang isang semaphore. Ang pinakamahalagang bagay ay na kung tayo ay magkakaroon ng access, kung gayon ang semaphore ay dapat may positibong bilang ng mga permit. Ang bilang na ito ay maaaring masimulan sa isang negatibong numero. At maaari kaming humiling (makakuha) ng higit sa 1 permit.
CountDownLatch
Ang susunod na mekanismo ay
CountDownLatch
. Hindi nakakagulat, ito ay isang latch na may countdown. Dito kailangan natin ang naaangkop na pahayag sa pag-import para sa
java.util.concurrent.CountDownLatch
klase. Ito ay tulad ng isang karera ng paa, kung saan ang lahat ay nagtitipon sa panimulang linya. At kapag handa na ang lahat, sabay-sabay na natatanggap ng lahat ang panimulang signal at sabay na magsisimula. Halimbawa:
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
Runnable task = () -> {
try {
countDownLatch.countDown();
System.out.println("Countdown: " + countDownLatch.getCount());
countDownLatch.await();
System.out.println("Finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
Una, sasabihin muna namin ang trangka sa
countDown()
. Tinukoy ng Google ang countdown bilang "isang pagkilos ng pagbibilang ng mga numero sa reverse order sa zero." At pagkatapos ay sasabihin namin ang latch sa
await()
, ibig sabihin, maghintay hanggang ang counter ay maging zero. Kapansin-pansin, ito ay isang beses na counter. Ang dokumentasyon ng Java ay nagsasabing, "Kapag ang mga thread ay kailangang paulit-ulit na magbilang sa ganitong paraan, sa halip ay gumamit ng CyclicBarrier". Sa madaling salita, kung kailangan mo ng reusable counter, kailangan mo ng ibang opsyon:
CyclicBarrier
.
CyclicBarrier
Gaya ng ipinahihiwatig ng pangalan,
CyclicBarrier
ay isang "muling magamit" na hadlang. Kakailanganin nating i-import ang
java.util.concurrent.CyclicBarrier
klase. Tingnan natin ang isang halimbawa:
public static void main(String[] args) throws InterruptedException {
Runnable action = () -> System.out.println("On your mark!");
CyclicBarrier barrier = new CyclicBarrier(3, action);
Runnable task = () -> {
try {
barrier.await();
System.out.println("Finished");
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("Limit: " + barrier.getParties());
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
Tulad ng nakikita mo, pinapatakbo ng thread ang
await
pamamaraan, ibig sabihin, naghihintay ito. Sa kasong ito, bumababa ang halaga ng hadlang. Ang hadlang ay itinuturing na sira (
barrier.isBroken()
) kapag ang countdown ay umabot sa zero. Upang i-reset ang hadlang, kailangan mong tawagan ang
reset()
pamamaraan, na
CountDownLatch
wala.
Exchanger
Ang susunod na mekanismo ay Exchanger. Sa kontekstong ito, ang Exchange ay isang punto ng pag-synchronize kung saan ang mga bagay ay nagbabago o ipinagpapalit. Gaya ng inaasahan mo, ang an
Exchanger
ay isang klase na nagsasagawa ng palitan o swap. Tingnan natin ang pinakasimpleng halimbawa:
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Runnable task = () -> {
try {
Thread thread = Thread.currentThread();
String withThreadName = exchanger.exchange(thread.getName());
System.out.println(thread.getName() + " exchanged with " + withThreadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
}
Dito tayo magsisimula ng dalawang thread. Ang bawat isa sa kanila ay nagpapatakbo ng paraan ng palitan at naghihintay para sa kabilang thread na patakbuhin din ang paraan ng palitan. Sa paggawa nito, ipinagpapalit ng mga thread ang mga naipasa na argumento. Interesting. Hindi ba ito nagpapaalala sa iyo ng isang bagay? Ito ay nagpapaalala sa
SynchronousQueue
, na nasa puso ng
CachedThreadPool
. Para sa kalinawan, narito ang isang halimbawa:
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Runnable task = () -> {
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
queue.put("Message");
}
Ipinapakita ng halimbawa na kapag nagsimula ang isang bagong thread, maghihintay ito, dahil walang laman ang pila. At pagkatapos ay inilalagay ng pangunahing thread ang string na "Mensahe" sa pila. Higit pa rito, titigil din ito hanggang sa matanggap ang string na ito mula sa pila. Maaari mo ring basahin ang "
SynchronousQueue vs Exchanger " upang makahanap ng higit pa tungkol sa paksang ito.
Phaser
Nai-save namin ang pinakamahusay para sa huling —
Phaser
. Kakailanganin nating i-import ang
java.util.concurrent.Phaser
klase. Tingnan natin ang isang simpleng halimbawa:
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser();
// By calling the register method, we register the current (main) thread as a party
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
// After 3 seconds, we arrive at the barrier and deregister. Number of arrivals = number of registrations = start
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
// We indicate that there will be a +1 party on the Phaser
phaser.register();
// Start a new thread
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance(); // The threads register arrival at the phaser.
System.out.println(name + " after passing barrier");
}).start();
}
Inilalarawan ng halimbawa na kapag gumagamit ng
Phaser
, masisira ang hadlang kapag tumugma ang bilang ng mga pagpaparehistro sa bilang ng mga dumating sa hadlang. Maaari kang maging mas pamilyar
Phaser
sa pamamagitan ng pagbabasa
nitong GeeksforGeeks na artikulo .
Buod
Tulad ng nakikita mo mula sa mga halimbawang ito, mayroong iba't ibang paraan upang i-synchronize ang mga thread. Kanina, sinubukan kong alalahanin ang mga aspeto ng multithreading. Sana ay naging kapaki-pakinabang ang mga nakaraang installment sa seryeng ito. Ang ilang mga tao ay nagsasabi na ang landas sa multithreading ay nagsisimula sa aklat na "Java Concurrency in Practice". Bagama't ito ay inilabas noong 2006, sinasabi ng mga tao na ang aklat ay medyo foundational at may kaugnayan pa rin ngayon. Halimbawa, maaari mong basahin ang talakayan dito:
May bisa pa ba ang "Java Concurrency In Practice"? . Kapaki-pakinabang din na basahin ang mga link sa talakayan. Halimbawa, may link sa aklat na
The Well-Grounded Java Developer , at partikular na babanggitin namin ang
Kabanata 4. Modern concurrency . Mayroon ding isang buong pagsusuri tungkol sa paksang ito:
Wasto pa rin ba ang "Java Concurrency in Practice" sa Panahon ng Java 8? Nag-aalok din ang artikulong iyon ng mga mungkahi tungkol sa kung ano pa ang babasahin upang tunay na maunawaan ang paksang ito. Pagkatapos nito, maaari mong tingnan ang isang mahusay na aklat tulad ng
OCA/OCP Java SE 8 Programmer Practice Tests . Interesado kami sa pangalawang acronym: OCP (Oracle Certified Professional). Makakakita ka ng mga pagsubok sa "Kabanata 20: Java Concurrency". Ang aklat na ito ay may parehong mga tanong at sagot na may mga paliwanag. Halimbawa:
Maraming tao ang maaaring magsimulang magsabi na ang tanong na ito ay isa pang halimbawa ng pagsasaulo ng mga pamamaraan. Sa isang banda, oo. Sa kabilang banda, masasagot mo ang tanong na ito sa pamamagitan ng pag-alala na iyon
ExecutorService
ay isang uri ng "pag-upgrade" ng
Executor
. At
Executor
ay inilaan upang itago lamang ang paraan ng paggawa ng mga thread, ngunit hindi ito ang pangunahing paraan upang maisagawa ang mga ito, iyon ay, magsimula ng isang
Runnable
bagay sa isang bagong thread. Iyon ang dahilan kung bakit walang
execute(Callable)
— dahil sa
ExecutorService
, ang
Executor
simpleng nagdaragdag ng
submit()
mga pamamaraan na maaaring magbalik ng isang
Future
bagay. Siyempre, maaari naming kabisaduhin ang isang listahan ng mga pamamaraan, ngunit mas madaling gawin ang aming sagot batay sa aming kaalaman sa likas na katangian ng mga klase mismo. At narito ang ilang karagdagang mga materyales sa paksa:
Mas mahusay na magkasama: Java at ang klase ng Thread. Bahagi I — Mga Thread ng execution Mas mahusay na magkasama: Java at ang Thread class. Bahagi II — Pag-synchronize Mas mahusay na magkasama: Java at ang Thread na klase. Part III — Mas Mahusay na Pakikipag-ugnayan: Java at ang Thread class. Bahagi IV — Matatawagan, Hinaharap, at mga kaibigan Mas mahusay na magkasama: Java at ang Thread na klase. Bahagi V — Tagapagpatupad, ThreadPool, Fork/Join
GO TO FULL VERSION