介绍
线程是一个有趣的东西。在过去的评论中,我们研究了一些用于实现多线程的可用工具。让我们看看我们还能做些什么有趣的事情。在这一点上,我们知道了很多。例如,从《
Better together: Java and the Thread class. Part I — Threads of execution》中,我们知道 Thread 类代表一个执行线程。我们知道线程执行一些任务。如果我们希望我们的任务能够
run
,那么我们必须用 标记线程
Runnable
。
要记住,我们可以使用
Tutorialspoint 在线 Java 编译器:
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();
}
我们也知道我们有一种叫做锁的东西。
我们在“ Better together: Java and the Thread class. Part II — Synchronization”中了解了这一点。如果一个线程获取锁,那么另一个试图获取锁的线程将被迫等待锁被释放:
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();
}
}
我认为是时候谈谈我们可以做哪些其他有趣的事情了。
信号量
控制多少线程可以同时运行的最简单方法是信号量。这就像一个铁路信号。绿色表示继续。红色表示等待。等待信号量的什么?使用权。要获得访问权限,我们必须获取它。当不再需要访问时,我们必须放弃或释放它。让我们看看这是如何工作的。我们需要导入
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
。不出所料,这是一个带倒计时的闩锁。这里我们需要为该类提供适当的导入语句
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();
}
}
首先,我们先告诉latch to
countDown()
。谷歌将倒计时定义为“将数字倒序计数到零的行为”。然后我们告诉锁存器到
await()
,即等到计数器变为零。有趣的是,这是一个一次性计数器。Java 文档说,“当线程必须以这种方式重复倒计时时,请改用 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。在此上下文中,Exchange 是一个同步点,在该同步点中可以交换或交换事物的变化。如您所料,an
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() + " exchanged with " + withThreadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
}
这里我们启动两个线程。它们中的每一个都运行 exchange 方法并等待另一个线程也运行 exchange 方法。这样做时,线程交换传递的参数。有趣的。是不是让你想起了什么?它让人想起
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");
}
这个例子表明,当一个新线程启动时,它会等待,因为队列是空的。然后主线程将“Message”字符串放入队列中。更重要的是,它也会停止,直到从队列中收到这个字符串。您还可以阅读“
SynchronousQueue vs 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 文章来更加熟悉。
概括
从这些示例中可以看出,有多种方法可以同步线程。早些时候,我试图回忆多线程的各个方面。我希望本系列的前几期文章对您有所帮助。有人说,多线程之路是从《Java Concurrency in Practice》这本书开始的。虽然它是 2006 年出版的,但人们说这本书非常基础并且今天仍然适用。例如,您可以阅读此处的讨论:
“Java Concurrency In Practice”是否仍然有效?. 阅读讨论中的链接也很有用。例如,有一个指向
The Well-Grounded Java Developer一书的链接,我们将特别提到
第 4 章现代并发性。还有关于此主题的完整评论:
《Java 并发实战》在 Java 8 时代还有效吗?该文章还提供了有关阅读其他内容以真正理解该主题的建议。在那之后,你可以看看像
OCA/OCP Java SE 8 Programmer Practice Tests这样的好书。我们对第二个首字母缩略词感兴趣:OCP(Oracle 认证专家)。您将在“第 20 章:Java 并发”中找到测试。这本书既有问题也有答案,并有解释。例如:
很多人可能会开始说这道题又是记忆方法的例子。一方面,是的。另一方面,您可以通过回忆那
ExecutorService
是 . 的一种“升级”来回答这个问题
Executor
。和
Executor
旨在简单地隐藏线程的创建方式,但它并不是执行它们的主要方式,即
Runnable
在新线程上启动一个对象。这就是为什么没有
execute(Callable)
— 因为在 中
ExecutorService
,
Executor
只需添加
submit()
可以返回
Future
对象的方法。当然,我们可以记住一个方法列表,但是根据我们对类本身性质的了解来做出答案要容易得多。以下是有关该主题的一些其他材料:
更好的结合:Java 和 Thread 类。第 I 部分 — 执行的线程 更好地结合:Java 和 Thread 类。第二部分 — 同步 更好地结合:Java 和 Thread 类。第 III 部分 — 更好地交互:Java 和 Thread 类。第 IV 部分 — Callable、Future 和朋友 更好地结合在一起:Java 和 Thread 类。第五部分 — 执行器、ThreadPool、Fork/Join
GO TO FULL VERSION