介绍
流是一件有趣的事情。在之前的评论中,我们研究了一些用于实现多线程的可用工具。让我们看看我们还能做哪些有趣的事情。此时我们知道了很多。例如,从“
你不能用线程破坏 Java:第一部分 - 线程”中,我们知道线程就是线程。我们知道一个线程正在执行某些任务。如果我们希望我们的任务能够运行(
run
),那么我们必须将线程指定为某个
Runnable
。
要记住,我们可以使用
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源自英语,译为交换或交换。A
Exchanger
是交换者,即他们通过某种东西进行交换。让我们看一个简单的例子:
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();
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
phaser.register();
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance();
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。并且“∫”中有测试。本书包含问题和答案以及解释。例如:
许多人可能会开始说这只是另一种方法的记忆。一方面,是的。另一方面,这个问题可以通过记住
ExecutorService
这是一种“升级”来回答
Executor
。它
Executor
的目的只是隐藏创建线程的方法,而不是隐藏执行线程的主要方式,即在新线程中运行
Runnable
。因此,
execute(Callable)
不,因为 他们只是添加了
ExecutorService
可以返回的方法。正如您所看到的,我们可以记住方法列表,但如果我们知道类本身的性质,猜测会容易得多。好吧,关于这个主题的一些附加材料:
Executor
submit
Future
#维亚切斯拉夫
GO TO FULL VERSION