Introduktion
Trådar är en intressant sak. I tidigare recensioner tittade vi på några av de tillgängliga verktygen för att implementera multithreading. Låt oss se vilka andra intressanta saker vi kan göra. Vid det här laget vet vi mycket. Till exempel, från "
Bättre tillsammans: Java och klassen Thread. Del I — Threads of execution " vet vi att Thread-klassen representerar en exekveringstråd. Vi vet att en tråd utför någon uppgift. Om vi vill att våra uppgifter ska kunna
run
så måste vi markera tråden med
Runnable
.
![Bättre tillsammans: Java och trådklassen. Del VI — Skjut loss! - 1]()
För att komma ihåg kan vi använda
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();
}
Vi vet också att vi har något som kallas lås. Vi lärde oss om detta i "
Bättre tillsammans: Java och trådklassen. Del II — Synkronisering . Om en tråd skaffar ett lås, kommer en annan tråd som försöker få låset att tvingas vänta på att låset släpps:
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();
}
}
Jag tycker att det är dags att prata om vilka andra intressanta saker vi kan göra.
Semaforer
Det enklaste sättet att kontrollera hur många trådar som kan köras samtidigt är en semafor. Det är som en järnvägssignal. Grönt betyder fortsätt. Rött betyder vänta. Vänta på vad från semaforen? Tillgång. För att få tillgång måste vi skaffa det. Och när åtkomst inte längre behövs måste vi ge bort eller släppa det. Låt oss se hur det här fungerar. Vi måste importera
java.util.concurrent.Semaphore
klassen. Exempel:
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);
}
Som du kan se hjälper dessa operationer (förvärva och släpp) oss att förstå hur en semafor fungerar. Det viktigaste är att om vi ska få tillträde så måste semaforen ha ett positivt antal tillstånd. Detta antal kan initieras till ett negativt tal. Och vi kan begära (förvärva) mer än 1 tillstånd.
CountdownLatch
Nästa mekanism är
CountDownLatch
. Föga överraskande är detta en spärr med nedräkning. Här behöver vi lämplig importsats för klassen
java.util.concurrent.CountDownLatch
. Det är som ett fotlopp, där alla samlas vid startlinjen. Och när alla är redo får alla startsignalen samtidigt och startar samtidigt. Exempel:
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();
}
}
Först säger vi först till spärren att
countDown()
. Google definierar nedräkning som "en handling av att räkna siffror i omvänd ordning till noll". Och sedan säger vi till spärren till
await()
, dvs vänta tills räknaren blir noll. Intressant nog är detta en engångsräknare. Java-dokumentationen säger, "När trådar upprepade gånger måste räkna ner på detta sätt, använd istället en CyclicBarrier". Med andra ord, om du behöver en återanvändbar disk behöver du ett annat alternativ:
CyclicBarrier
.
CyclicBarrier
Som namnet antyder,
CyclicBarrier
är en "återanvändbar" barriär. Vi måste importera
java.util.concurrent.CyclicBarrier
klassen. Låt oss titta på ett exempel:
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();
}
}
Som ni ser kör tråden
await
metoden, dvs den väntar. I detta fall minskar barriärvärdet. Barriären anses bruten (
barrier.isBroken()
) när nedräkningen når noll. För att återställa barriären måste du anropa
reset()
metoden, som
CountDownLatch
inte har.
Växlare
Nästa mekanism är Exchanger. I det här sammanhanget är en Exchange en synkroniseringspunkt där saker som förändras kan bytas eller bytas. Som du kan förvänta dig
Exchanger
är an en klass som utför ett utbyte eller swap. Låt oss titta på det enklaste exemplet:
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();
}
Här startar vi två trådar. Var och en av dem kör utbytesmetoden och väntar på att den andra tråden också kör utbytesmetoden. På så sätt utbyter trådarna de godkända argumenten. Intressant. Påminner det dig inte om något? Det påminner om
SynchronousQueue
, som ligger i hjärtat av
CachedThreadPool
. För tydlighetens skull, här är ett exempel:
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");
}
Exemplet visar att när en ny tråd startas kommer den att vänta, eftersom kön blir tom. Och sedan lägger huvudtråden "Meddelande"-strängen i kön. Dessutom kommer den också att stanna tills den här strängen tas emot från kön. Du kan också läsa "
SynchronousQueue vs Exchanger " för att hitta mer om detta ämne.
Phaser
Vi har sparat det bästa till sist —
Phaser
. Vi måste importera
java.util.concurrent.Phaser
klassen. Låt oss titta på ett enkelt exempel:
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();
}
Exemplet illustrerar att när man använder
Phaser
, spricker bommen när antalet registreringar matchar antalet ankomster till bommen. Du kan bli mer bekant med
Phaser
genom att läsa
denna GeeksforGeeks-artikel .
Sammanfattning
Som du kan se av dessa exempel finns det olika sätt att synkronisera trådar. Tidigare försökte jag komma ihåg aspekter av multithreading. Jag hoppas att de tidigare avsnitten i den här serien var användbara. Vissa säger att vägen till multithreading börjar med boken "Java Concurrency in Practice". Även om den släpptes 2006, säger folk att boken är ganska grundläggande och fortfarande relevant idag. Du kan till exempel läsa diskussionen här:
Är "Java Concurrency In Practice" fortfarande giltig? . Det är också bra att läsa länkarna i diskussionen. Till exempel finns det en länk till boken
The Well-Grounded Java Developer , och vi ska särskilt nämna
kapitel 4. Modern concurrency . Det finns också en hel recension om detta ämne:
Är "Java Concurrency in Practice" fortfarande giltigt i Java 8:s era? Den artikeln ger också förslag om vad mer du kan läsa för att verkligen förstå detta ämne. Efter det kan du ta en titt på en bra bok som
OCA/OCP Java SE 8 Programmer Practice Tests . Vi är intresserade av den andra akronymen: OCP (Oracle Certified Professional). Du hittar tester i "Kapitel 20: Java Concurrency". Den här boken har både frågor och svar med förklaringar. Till exempel:
![Bättre tillsammans: Java och trådklassen. Del VI — Skjut loss! - 3]()
Många kanske börjar säga att den här frågan är ännu ett exempel på memorering av metoder. Å ena sidan, ja. Å andra sidan kan du svara på den här frågan genom att komma ihåg att det
ExecutorService
är en slags "uppgradering" av
Executor
. Och
Executor
är avsett att helt enkelt dölja hur trådar skapas, men det är inte det huvudsakliga sättet att köra dem, det vill säga starta ett
Runnable
objekt på en ny tråd. Det är därför det inte finns någon
execute(Callable)
— för i
ExecutorService
, lägger den
Executor
helt enkelt till
submit()
metoder som kan returnera ett
Future
objekt. Naturligtvis kan vi memorera en lista med metoder, men det är mycket lättare att göra vårt svar baserat på vår kunskap om själva klassernas karaktär. Och här är ytterligare material om ämnet:
Bättre tillsammans: Java och trådklassen. Del I — Trådar av utförande Bättre tillsammans: Java och klassen Thread. Del II — Synkronisering Bättre tillsammans: Java och klassen Thread. Del III — Interaktion Bättre tillsammans: Java och klassen Thread. Del IV — Callable, Future och friends Bättre tillsammans: Java och Thread-klassen. Del V — Executor, ThreadPool, Fork/Join
GO TO FULL VERSION