JavaRush /Java 博客 /Random-ZH /你不能用线程破坏 Java:第六部分 - 到达障碍!
Viacheslav
第 3 级

你不能用线程破坏 Java:第六部分 - 到达障碍!

已在 Random-ZH 群组中发布

介绍

流是一件有趣的事情。在之前的评论中,我们研究了一些用于实现多线程的可用工具。让我们看看我们还能做哪些有趣的事情。此时我们知道了很多。例如,从“你不能用线程破坏 Java:第一部分 - 线程”中,我们知道线程就是线程。我们知道一个线程正在执行某些任务。如果我们希望我们的任务能够运行( run),那么我们必须将线程指定为某个Runnable你不能用线程破坏 Java:第六部分 - 到达障碍! - 1要记住,我们可以使用Tutorialspoint Java Online 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:第二部分 - 同步》中读到了相关内容。一个线程可以占用一个锁,然后另一个尝试占用该锁的线程将被迫等待该锁释放:
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();
	}
}
我认为是时候谈谈我们还可以做哪些有趣的事情了。

信号量

控制可以同时工作的线程数量的最简单方法是信号量。就像在铁路上一样。绿灯亮了——你可以。红灯亮了——我们正在等待。我们对信号量有何期望?权限。英语许可 - 许可。要获得许可,您需要获得它,这在英语中是“acquire”。而当不再需要该许可时,我们必须将其放弃,即释放它或摆脱它,英语中将是释放。让我们看看它是如何工作的。我们需要导入该类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);
}
正如我们所看到的,记住英语单词后,我们就了解了信号量的工作原理。有趣的是,主要条件是信号量“帐户”必须具有正数的许可证。因此,您可以用减号来启动它。并且您可以请求(获取)超过 1 个。

倒计时锁存器

下一个机制是CountDownLatch。英文中的CountDown是倒计时,Latch是螺栓或闩锁。也就是说,如果我们翻译一下,那么这是一个带有倒计时的锁存器。这里我们需要适当的导入该类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();
 	}
}
英语中的await——期待。也就是我们先说话countDown。正如谷歌翻译所说,倒计时是“将数字逆序计数到零的行为”,即执行倒计时动作,其目的是数到零。然后我们说await- 即等待计数器值变为零。有趣的是,这样的柜台是一次性的。正如JavaDoc中所说的——“当线程必须以这种方式重复计数时,请改为使用CyclicBarrier”,也就是说,如果您需要可重复使用的计数,则需要使用另一个选项,称为CyclicBarrier.

循环势垒

顾名思义,CyclicBarrier它是一个周期性障碍。我们需要导入该类java.util.concurrent.CyclicBarrier。让我们看一个例子:
public static void main(String[] args) throws InterruptedException {
	Runnable action = () -> System.out.println("На старт!");
	CyclicBarrier berrier = new CyclicBarrier(3, action);
	Runnable task = () -> {
		try {
			berrier.await();
			System.out.println("Finished");
		} catch (BrokenBarrierException | InterruptedException e) {
			e.printStackTrace();
		}
	};
	System.out.println("Limit: " + berrier.getParties());
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
	}
}
可以看到,线程正在执行await,即等待。在这种情况下,障碍的价值会降低。berrier.isBroken()当倒计时达到零时,障碍被视为已损坏 ( )。要重置障碍,您需要调用berrier.reset(),而 中缺少该函数CountDownLatch

交换器

下一个补救措施是Exchanger。Exchange源自英语,译为交换或交换。AExchanger是交换者,即他们通过某种东西进行交换。让我们看一个简单的例子:
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() + " обменялся с " + withThreadName);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	new Thread(task).start();
}
这里我们启动两个线程。它们中的每一个都执行交换方法并等待另一个线程也执行交换方法。因此,线程将在它们之间交换传递的参数。有趣的事情。她没有提醒你什么吗?他提醒说SynchronousQueue,这是“a”的核心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");
}
该示例显示,通过启动一个新线程,该线程将进入等待模式,因为 队列将为空。然后main线程会将文本“Message”排队。同时,它将停止所需的时间,直到从队列中接收到该文本元素。关于这个主题,您还可以阅读“ SynchronousQueue Vs Exchanger ”。

移相器

最后,最甜蜜的事情—— Phaser。我们需要导入该类java.util.concurrent.Phaser。让我们看一个简单的例子:
public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new Phaser();
        // Вызывая метод register, мы регистрируем текущий поток (main) How участника
        phaser.register();
        System.out.println("Phasecount is " + phaser.getPhase());
        testPhaser(phaser);
        testPhaser(phaser);
        testPhaser(phaser);
        // Через 3 секунды прибываем к барьеру и снимаемся регистрацию. Кол-во прибывших = кол-во регистраций = пуск
        Thread.sleep(3000);
        phaser.arriveAndDeregister();
        System.out.println("Phasecount is " + phaser.getPhase());
    }

    private static void testPhaser(final Phaser phaser) {
        // Говорим, что будет +1 участник на Phaser
        phaser.register();
        // Запускаем новый поток
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " arrived");
            phaser.arriveAndAwaitAdvance(); //threads register arrival to the phaser.
            System.out.println(name + " after passing barrier");
        }).start();
    }
该示例表明,使用Phaser“a”时,当注册数量与到达屏障的数量一致时,屏障就会被破坏。您可以在“新 Phaser 同步器Phaser”中心的文章中找到更多信息。

结果

正如您从示例中看到的,同步线程有多种方法。早些时候我试图记住一些有关多线程的知识,我希望前面的部分有用。他们说多线程之路始于《Java并发实践》一书。虽然这本书是 2006 年出版的,但人们反映这本书很基础,而且仍然很有冲击力。例如,您可以阅读此处的讨论:“ Java 并发实践中仍然有效吗? ”。阅读讨论中的链接也很有帮助。例如,有一个“ The Well-Grounded Java Developer ”一书的链接,其中值得关注的“ Chapter 4. Modern concurrency ”。关于同一主题还有另一篇完整的评论:“ Java 并发实践在 Java 8 时代是否仍然相关”。它还提供了有关您还应该阅读哪些内容才能真正理解该主题的提示。之后,您可以仔细看看《OCA OCP JavaSE 8程序员实践测试》这样一本精彩的书。我们感兴趣的是第二部分,即OCP。并且“∫”中有测试。本书包含问题和答案以及解释。例如: 你不能用线程破坏 Java:第六部分 - 到达障碍! - 3许多人可能会开始说这只是另一种方法的记忆。一方面,是的。另一方面,这个问题可以通过记住ExecutorService这是一种“升级”来回答Executor。它Executor的目的只是隐藏创建线程的方法,而不是隐藏执行线程的主要方式,即在新线程中运行Runnable。因此,execute(Callable)不,因为 他们只是添加了ExecutorService可以返回的方法。正如您所看到的,我们可以记住方法列表,但如果我们知道类本身的性质,猜测会容易得多。好吧,关于这个主题的一些附加材料: ExecutorsubmitFuture #维亚切斯拉夫
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION