Java 多线程加法求和
代码
先上代码再上解析:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Sum implements Runnable {
private final int[] numbers;
private final int start;
private final int end;
private final AtomicInteger total;
public Sum(int[] numbers, int start, int end, AtomicInteger total) {
this.numbers = numbers;
this.start = start;
this.end = end;
this.total = total;
}
@Override
public void run() {
int localSum = 0;
for (int i = start; i < end; i++) {
localSum += numbers[i];
}
// addAndGet 可以原子地(即线程安全地)将给定的数值增加到当前数值
System.out.printf("local %d\n", localSum);
total.addAndGet(localSum);
}
}
class MultiThreadedAddition {
public static void main(String[] args) throws InterruptedException {
// 实现从1加到100
int[] numbers = new int[101];
for (int i = 1; i < numbers.length; i++) {
numbers[i] = i;
}
AtomicInteger total = new AtomicInteger();
// 假设有4个线程并发计算
int threadCount = 4;
// Executors.newFixedThreadPool 用于创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
// 把 numbers.length 个(100个)任务均分给 threadCount 个(4个)线程
int partitionSize = numbers.length / threadCount;
for (int i = 0; i < threadCount; i++) {
int startIndex = i * partitionSize;
// 这里要特判一下最后一个线程的结束范围,因为可能不能恰好均分任务,比如999个任务给4个线程做...
int endIndex = (i == threadCount - 1) ? numbers.length : (i + 1) * partitionSize;
Sum task = new Sum(numbers, startIndex, endIndex, total);
executorService.submit(task);
}
// 先关闭线程池
executorService.shutdown();
// 阻塞主线程,让其等待所有线程提交结果(或者直到超过最大等待时间,不过这里设置的是MAX_VALUE,就是无限等待
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
System.out.println(total.get());
}
}
运行结果
这里是做了一个1~100的求和,我们知道结果是5050,运行检验一下:
多线程的要点解析
多线程创建:Runnable Callable Thread
Runnable 接口
- 基本概念
Runnable 是一个非常基础的接口,只有一个方法void run()
。任何实现了 Runnable 接口的类都可以作为一个线程的工作单元(任务)。 - 传递给 Thread 类的构造函数
当一个 Runnable 实例被传给 Thread 构造函数时,可以创建一个新的线程,然后调用 Thread.start() 方法启动该线程来执行 run() 方法中的代码。 - 没有返回值
Runnable 不支持返回值,并且在 run() 方法内部抛出的异常不会被捕获并转发给调用者。
Thread 类
- 基本概念
Thread 是Java中用于实现线程的基础类,它实现了 Runnable 接口,所以可以通过继承 Thread 类并重写 run() 方法来创建一个可运行的线程。 - 接收 Runnable 实例
呼应上文
通过创建 Thread 类的子类或通过传递一个 Runnable 对象给 Thread 构造函数,可以创建和管理线程。
3. 更多 API
Thread 类提供了更多控制线程生命周期的方法,如 start()、join()、interrupt()、setName() 等。
Callable 接口
-
基本概念
Callable 是在Java 1.5以后引入的,相比于 Runnable,它更加灵活。 -
能返回值和抛出异常
可以通过 call() 方法返回一个结果,而且 call() 方法可以抛出受检异常(checked exception)。
3. 使用
Callable 实例不能直接启动,需要通过 ExecutorService
提交给线程池执行,并通过 Future 对象获取异步计算的结果。
使用 Callable 通常结合 FutureTask(实现了 Runnable 和 Future 接口)来包装 Callable 任务,并将其提交给线程池执行,从而可以获得线程执行的结果。
结束线程的工作
上面的代码中结束线程工作的过程中,涉及到了两个API
executorService.shutdown()
相当于关闭线程池,不允许再扔新的线程或者任务进来了。executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS)
如果一个线程调用这个方法,那么它就会被阻塞,暂时停止活动直到这个线程池里面的所有线程结束工作,或者等待超过指定时间——这里我们设置了 Long.MAX_VALUE 和 TimeUnit.NANOSECONDS,前者是指 无限期等待,后者是指时间单位为 纳秒。
这里我们是主线程调用的,所以就是主线程被阻塞.
避免竞态
上述代码中的多线程加法求和用到了 AtomicInteger,它是 java.util.concurrent.atomic
包下原子整数类,顾名思义,它所对应整数上发生的操作都是原子性的,线程只能串行的去累加它,就能避免竞态问题。