大概是兩年前吧,朋友去美團(tuán)面試,考了他一道純工程的題目:
使用多線程實(shí)現(xiàn) 1 + 2 + 3 + ..... + 1000
確實(shí)是很好的一道面試題,這兩年面試別人,我也經(jīng)常會(huì)用這道題。相比于爛俗的鏈表反轉(zhuǎn),二分查找。這道題要有趣的多。
多線程解決問(wèn)題,首先的思路是把任務(wù)拆分。在這個(gè)題中,很容易想到兩種拆分方式(我們以拆成10份為例):
1 按范圍把1000個(gè)數(shù)分成10份,分別為
[1, 100]
[101, 200]
.....
[901, 1000]
2 每個(gè)數(shù)對(duì)10取模,分為10組
{1, 11, 21, 31.....991}
{2, 12, 22, .... 992}
....
{10, 20, 30, ..... 1000}
由于兩種方式?jīng)]有本質(zhì)的區(qū)別,為了簡(jiǎn)便,本文中使用第一種方式作為拆分方式實(shí)現(xiàn)。
1 最簡(jiǎn)單版本的實(shí)現(xiàn)
public static void main(String[] args) throws InterruptedException {
AtomicInteger sum = new AtomicInteger();
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
final int cur = i;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int j = cur * 100 + 1; j <= cur * 100 + 100; j++) {
sum.addAndGet(j);
}
}
});
thread.start();
threadList.add(thread);
}
for (Thread thread : threadList) {
thread.join();
}
System.out.println(sum.get());
}
代碼沒(méi)啥亮點(diǎn),但也沒(méi)什么漏洞。
改進(jìn)點(diǎn)是可以使用線程池代替線程數(shù)組,使用CountDownLatch來(lái)判定結(jié)束。
改進(jìn)版的實(shí)現(xiàn)如下:
2 線程池版
public static void main(String[] args) throws InterruptedException {
AtomicInteger sum = new AtomicInteger();
ExecutorService pool = Executors.newCachedThreadPool();
final CountDownLatch latch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
final int cur = i;
pool.execute(new Runnable() {
@Override
public void run() {
for (int j = cur * 100 + 1; j <= cur * 100 + 100; j++) {
sum.addAndGet(j);
}
latch.countDown();
}
});
}
latch.await();
System.out.println(sum.get());
}
用到了線程池,一定會(huì)想到j(luò)ava的future類(lèi)和callable,正好適合解決這種問(wèn)題。
3 future版
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newCachedThreadPool();
List<Future<Integer>> futureList = new ArrayList<>();
int sum = 0;
for (int i = 0; i < 10; i++) {
final int cur = i;
futureList.add(pool.submit(new Callable<Integer>() {
@Override
public Integer call() {
int segSum = 0;
for (int j = cur * 100 + 1; j <= cur * 100 + 100; j++) {
segSum += j;
}
return segSum;
}
}));
}
for (int i = 0; i < 10; i++) {
sum += futureList.get(i).get();
}
System.out.println(sum);
}
上面的這幾種方式,都能正確得到答案,在工程上也基本沒(méi)有漏洞。擴(kuò)展性來(lái)說(shuō),也還算不錯(cuò)。
但總覺(jué)得中規(guī)中矩,沒(méi)有什么亮點(diǎn)。
不夠有趣。
前幾天看一篇分享,講stream.parallel的應(yīng)用場(chǎng)景。突然靈光一閃,發(fā)現(xiàn)用在這個(gè)問(wèn)題上簡(jiǎn)直是量身定做一般。
代碼如下:
public static void main(String[] args) {
System.out.println(IntStream.range(0, 10)
.boxed()
.parallel()
.mapToInt(i -> IntStream.rangeClosed(i * 100 + 1, i * 100 + 100).sum())
.sum());
}
其實(shí)還可以更簡(jiǎn)化的:
public static void main(String[] args) {
System.out.println(IntStream.rangeClosed(0, 1000)
.parallel()
.sum());
}
have fun