我们将研究的第一个模型是“单线程顺序模型”。这是最简单的编程风格。每项任务一次执行一项,其中一项任务在开始之前完成。这就像你只有一个工人来完成所有三个任务,如果给了一个任务,工人将完成它直到它终止,然后再拿起任何其他任务。
让我们考虑一个人做饭。他想煮面包煎蛋卷。他开始煮鸡蛋,先等它煮熟,然后开始烤面包,然后吃掉。
class SequentialExecutionSingleThreaded {
public static void main(String[] args) throws InterruptedException {
long sum = 0;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println("Sum : " + sum);
// Output
// Sum : 2305843008139952128
}
}
另一个模型是多线程顺序模型。在这个模型中,主线程将任务分配给子线程/工作线程,但同时阻塞它们以完成执行,然后再将另一个任务分配给其他工作线程。
用上面做面包煎蛋卷的类比,假设一个人开始煮鸡蛋,煮熟后她妈妈会烤面包。这些任务一个接一个地由不同的人(线程)执行。
class SequentialExecutionMultiThreaded {
static long sum = 0;
public static void getSum(long startRange, long endRange) {
System.out.println("Using thread : " + Thread.currentThread().getName());
for (long i = startRange; i <= endRange; i++) {
sum += i;
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> getSum(0, Integer.MAX_VALUE / 2));
Thread t2 = new Thread(() -> getSum(Integer.MAX_VALUE / 2 + 1, Integer.MAX_VALUE));
t1.start();
t1.join(); // waiting for the result to be computed by blocking main thread
t2.start();
t2.join(); // waiting for the result to be computed by blocking main thread
System.out.println("Sum : " + sum);
/*
* Output :
* Using thread : Thread-0
* Using thread : Thread-1
* Sum : 2305843008139952128
*/
}
}
使用flatMap执行未来组合的类似且更现代的代码:
import java.util.concurrent.*;
import java.util.function.BiFunction;
import java.util.function.Supplier;
class SequentialExecutionMultiThreaded {
static ExecutorService executor = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
getSum(0, 0, Integer.MAX_VALUE / 2)
.thenCompose(value -> getSum(value, Integer.MAX_VALUE / 2 + 1, Integer.MAX_VALUE))
.thenAccept(value -> System.out.println("Sum : " + value));
}
public static CompletableFuture getSum(long initialValue, int startRange, int endRange) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("Using thread : " + Thread.currentThread().getName());
long sum = 0;
for (long i = startRange; i <= endRange; i++) {
sum += i;
}
return initialValue + sum;
}, executor);
};
}
并发是一次处理很多事情的能力。让我用一个简单的比喻来解释。
让我们考虑一个人在慢跑。在他慢跑的过程中,假设他的鞋带解开了。他停下来系鞋带,然后又开始跑。这是并发的经典示例,因为人们能够同时处理跑步和系鞋带。
传统上,我们一直使用多个线程一次处理多个任务,每个线程都在自己的单独任务上取得进展。
单线程并发意味着在单个线程内一次处理多个任务。Node.js使这种模式流行起来,因为它可以执行真正的非阻塞 I/O 并以更少的内存占用实现更高的并发性。
回到同样的面包煎蛋卷类比,我把鸡蛋煮沸并设置一个计时器,我把面包烤好,然后启动另一个计时器,当它们完成后,我就可以吃了。
现在我们可以看到同步模型和异步模型的含义了。
同步执行意味着启动任务的线程需要等待任务完成才能继续工作,异步执行意味着任务在未来的某个时间点执行,无需等待任何事情完成.
异步并不意味着在另一个线程中。举个例子
List li = new ArrayList<>(Arrays.asList("z","y","x"));
li.sort(Comparator.naturalOrder());
通过提供内置比较器,我们按升序对字符串列表进行排序。问题是
这个比较器什么时候执行?在未来的某个时候。
它在哪个线程中执行?调用它的主线程或同一线程。
异步和并发是两个不同的概念。我们可以异步而不并发,并发并不一定意味着我们是异步的。
在此模型中,每个任务都在单独的控制线程中执行。在内核级 线程的情况下,线程由操作系统管理,在用户级线程(即绿色线程或协程)的情况下,由语言运行时管理。
回到同样的面包煎蛋卷类比,我雇了 2 名厨师,他们会煮鸡蛋并为我烤面包。
他们可以同时做到这一点,而且他们不必等待一个完成才能开始另一个,但他们正在同一个厨房使用一种资源做饭:
class ConcurrentExecutionMultiThreaded {
static long sum = 0;
public static void getSum(long startRange, long endRange) {
System.out.println("Using thread : " + Thread.currentThread().getName());
for (long i = startRange; i <= endRange; i++) {
sum += i;
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> getSum(0, Integer.MAX_VALUE / 2));
Thread t2 = new Thread(() -> getSum(Integer.MAX_VALUE / 2 + 1, Integer.MAX_VALUE));
t1.start();
t2.start(); core, they will run concurrently.
t1.join();
t2.join();
System.out.println("Sum : " + sum);
/*
* Output :
* Using thread : Thread-0
* Using thread : Thread-1
* Sum : 2305843008139952128
*/
}
}
使用 future获取执行结果
import java.util.concurrent.*;
class ConcurrentExecutionMultiThreaded {
static long sum = 0;
public static long getSum(long startRange, long endRange) {
System.out.println("Using thread : " + Thread.currentThread().getName());
long sum = 0;
for (long i = startRange; i <= endRange; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService service = Executors.newFixedThreadPool(2);
Future f1 = service.submit(() -> getSum(0, Integer.MAX_VALUE / 2));
Future f2 = service.submit(() -> getSum(Integer.MAX_VALUE / 2 + 1, Integer.MAX_VALUE));
long s1 = f1.get();
long s2 = f2.get();
System.out.println("Sum : " + (s1 + s2));
service.shutdown();
/*
* Output :
* Using thread : pool-1-thread-1
* Using thread : pool-1-thread-2
* Sum : 2305843008139952128
*/
}
}
并行性是同时做很多事情。它可能听起来类似于并发,但实际上是不同的。让我们举一个例子,你接到一个正在和他通话的朋友打来的电话,同时你又接到另一个朋友打来的电话。您将他们俩都安排在电话会议上,现在您正在同时与他们两个人交谈。
为了实现并行性,我们需要硬件支持,在这种情况下,需要多个 CPU 内核。
面包煎蛋卷的类比也是如此,你雇了 2 名厨师,他们会煮鸡蛋并为你烤面包。他们可以同时做到这一点,他们也不必等待一个完成才能开始另一个,但他们现在正在不同的厨房做饭,消耗不同的资源
| 留言与评论(共有 0 条评论) “” |