CodeGym /Java блог /Случаен /По-добре заедно: Java и клас Thread. Част VI — Изстрелвай...
John Squirrels
Ниво
San Francisco

По-добре заедно: Java и клас Thread. Част VI — Изстрелвай!

Публикувано в групата

Въведение

Конците са интересно нещо. В минали рецензии разгледахме някои от наличните инструменти за прилагане на многопоточност. Да видим 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
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION