Въведение
Конците са интересно нещо. В минали рецензии разгледахме някои от наличните инструменти за прилагане на многопоточност. Да видим Howви други интересни неща можем да направим. На този етап знаем много. Например от „
По-добре заедно: Java и клас Thread. Част I — Нишки за изпълнение “ знаем, че класът Thread представлява нишка за изпълнение. Знаем, че една нишка изпълнява няHowва задача. Ако искаме нашите задачи да могат
run
, тогава трябва да маркираме нишката с
Runnable
.
![По-добре заедно: Java и клас Thread. Част VI — Изстрелвай! - 1]()
За да запомните, можем да използваме
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();
}
Знаем също, че имаме нещо, наречено ключалка. Научихме за това в „ По-добре
заедно: Java и класът Thread. Част II — Синхронизация
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();
}
}
Мисля, че е време да поговорим Howви други интересни неща можем да направим.
Семафори
Най-простият начин да контролирате колко нишки могат да работят едновременно е семафор. Това е като железопътен сигнал. Зеленото означава продължете. Червеното означава изчакайте. Чакайте Howво от семафора? Достъп. За да получим достъп, трябва да го придобием. И когато достъпът вече не е необходим, трябва да го дадем or освободим. Нека да видим How работи това. Трябва да импортираме
java.util.concurrent.Semaphore
класа. Пример:
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);
}
Както можете да видите, тези операции (придобиване и освобождаване) ни помагат да разберем How работи един семафор. Най-важното е, че ако искаме да получим достъп, тогава семафорът трябва да има положителен брой разрешения. Този брой може да бъде инициализиран до отрицателно число. И можем да поискаме (получим) повече от 1 разрешително.
CountDownLatch
Следващият механизъм е
CountDownLatch
. Не е изненадващо, че това е резе с обратно броене. Тук се нуждаем от подходящ оператор за импортиране за
java.util.concurrent.CountDownLatch
класа. Това е като пешеходно състезание, където всички се събират на стартовата линия. И след като всички са готови, всички получават стартовия сигнал по едно и също време и стартират едновременно. Пример:
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();
}
}
Първо, първо казваме на резето да
countDown()
. Google определя обратното броене като „действие на броене на числа в обратен ред до нула“. И тогава казваме на ключалката да
await()
, т.е. да изчака докато броячът стане нула. Интересното е, че това е еднократен брояч. Документацията на Java казва: „Когато нишките трябва многократно да отброяват по този начин, instead of това използвайте CyclicBarrier“. С други думи, ако имате нужда от брояч за многократна употреба, имате нужда от различна опция:
CyclicBarrier
.
Циклична бариера
Както подсказва името,
CyclicBarrier
това е бариера за „повторна употреба“. Ще трябва да импортираме
java.util.concurrent.CyclicBarrier
класа. Да разгледаме един пример:
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();
}
}
Както можете да видите, нишката изпълнява
await
метода, т.е. чака. В този случай стойността на бариерата намалява. Бариерата се счита за счупена (
barrier.isBroken()
), когато обратното броене достигне нула. За да нулирате бариерата, трябва да извикате
reset()
метода, който
CountDownLatch
няма.
Обменник
Следващият механизъм е Exchanger. В този контекст обменът е точка за синхронизиране, където нещата се променят и се обменят. Както бихте очаквали, an
Exchanger
е клас, който извършва обмен or суап. Нека да разгледаме най-простия пример:
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();
}
Тук започваме две нишки. Всеки от тях изпълнява метода за обмен и чака другата нишка също да изпълни метода за обмен. При това нишките обменят предадените аргументи. интересно Не ви ли напомня нещо? Напомня на
SynchronousQueue
, което лежи в основата на
CachedThreadPool
. За по-голяма яснота ето един пример:
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");
}
Примерът показва, че когато се стартира нова нишка, тя ще изчака, защото опашката ще бъде празна. И след това основната нишка поставя низа "Съобщение" в опашката. Нещо повече, той също ще спре, докато този низ не бъде получен от опашката. Можете също да прочетете „
SynchronousQueue срещу Exchanger “, за да намерите повече по тази тема.
Фейзер
Запазихме най-доброто за накрая —
Phaser
. Ще трябва да импортираме
java.util.concurrent.Phaser
класа. Нека да разгледаме един прост пример:
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();
}
Примерът илюстрира, че когато се използва
Phaser
, бариерата се счупва, когато броят на регистрациите съвпадне с броя на пристигналите на бариерата. Можете да се запознаете по-добре,
Phaser
като прочетете
тази статия на GeeksforGeeks .
Резюме
Както можете да видите от тези примери, има различни начини за синхронизиране на нишки. По-рано се опитах да си спомня аспектите на многопоточността. Надявам се, че предишните части от тази серия са бor полезни. Някои хора казват, че пътят към многонишковостта започва с книгата "Java Concurrency на практика". Въпреки че е издадена през 2006 г., хората казват, че книгата е доста основополагаща и все още е актуална днес. Например, можете да прочетете дискусията тук:
Все още ли е валидна „Практиката на Java Concurrency“? . Също така е полезно да прочетете връзките в дискусията. Например, има връзка към книгата
The Well-Grounded Java Developer и ние ще споменем специално
Глава 4. Modern concurrency . Има и цяло ревю по тази тема:
Дали „Java Concurrency на практика“ все още е валиден в ерата на Java 8? Тази статия също предлага предложения Howво друго да прочетете, за да разберете наистина тази тема. След това можете да разгледате страхотна книга като
OCA/OCP Java SE 8 Programmer Practice Tests . Интересуваме се от втория акроним: OCP (Oracle Certified Professional). Ще намерите тестове в "Глава 20: Java Concurrency". Тази книга има Howто въпроси, така и отговори с обяснения. Например:
![По-добре заедно: Java и клас Thread. Част VI — Изстрелвай! - 3]()
Много хора може да започнат да казват, че този въпрос е още един пример за запомняне на методи. От една страна, да. От друга страна, можете да отговорите на този въпрос, като си припомните, че това
ExecutorService
е един вид „надстройка“ на
Executor
. И
Executor
има за цел просто да скрие начина, по който се създават нишки, но не е основният начин за тяхното изпълнение, тоест стартиране на обект
Runnable
в нова нишка. Ето защо няма
execute(Callable)
— защото в
ExecutorService
,
Executor
просто добавя
submit()
методи, които могат да върнат
Future
обект. Разбира се, можем да запомним списък с методи, но е много по-лесно да дадем своя отговор въз основа на нашите познания за природата на самите класове. А ето и допълнителни материали по темата:
По-добре заедно: Java и клас Thread. Част I — Нишки за изпълнение По-добре заедно: Java и класът Thread. Част II — По-добра синхронизация заедно: Java и класът Thread. Част III — Взаимодействието е по-добро заедно: Java и класът Thread. Част IV — Callable, Future и приятели По-добре заедно: Java и класът Thread. Част V — Изпълнител, ThreadPool, Fork/Join
GO TO FULL VERSION