CodeGym /Java 博客 /随机的 /更好的结合:Java 和 Thread 类。第六部分——开火!
John Squirrels
第 41 级
San Francisco

更好的结合:Java 和 Thread 类。第六部分——开火!

已在 随机的 群组中发布

介绍

线程是一个有趣的东西。在过去的评论中,我们研究了一些用于实现多线程的可用工具。让我们看看我们还能做些什么有趣的事情。在这一点上,我们知道了很多。例如,从《Better together: Java and the Thread class. Part I — Threads of execution》中,我们知道 Thread 类代表一个执行线程。我们知道线程执行一些任务。如果我们希望我们的任务能够run,那么我们必须用 标记线程Runnable更好的结合:Java 和 Thread 类。 第六部分——开火! - 1要记住,我们可以使用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 是一个同步点,在该同步点中可以交换或交换事物的变化。如您所料,anExchanger是执行交换或交换的类。让我们看一个最简单的例子:

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 并发”中找到测试。这本书既有问题也有答案,并有解释。例如: 更好的结合:Java 和 Thread 类。 第六部分——开火! - 3很多人可能会开始说这道题又是记忆方法的例子。一方面,是的。另一方面,您可以通过回忆那ExecutorService是 . 的一种“升级”来回答这个问题Executor。和Executor旨在简单地隐藏线程的创建方式,但它并不是执行它们的主要方式,即Runnable在新线程上启动一个对象。这就是为什么没有execute(Callable)— 因为在 中ExecutorServiceExecutor只需添加submit()可以返回Future对象的方法。当然,我们可以记住一个方法列表,但是根据我们对类本身性质的了解来做出答案要容易得多。以下是有关该主题的一些其他材料: 更好的结合:Java 和 Thread 类。第 I 部分 — 执行的线程 更好地结合:Java 和 Thread 类。第二部分 — 同步 更好地结合:Java 和 Thread 类。第 III 部分 — 更好地交互:Java 和 Thread 类。第 IV 部分 — Callable、Future 和朋友 更好地结合在一起:Java 和 Thread 类。第五部分 — 执行器、ThreadPool、Fork/Join
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION