Java并行无处不在。一般分为两种情况:
- 每个线程都在独立的状态环境下运行,说通俗一点就是,每个线程对应一套不同的Java对象;
- 所有线程都在一个无状态或状态不可变的环境下运行。
显然,第一种情况更占内存。如果可以设计成无状态或状态不可变环境,就尽量这么设计。最典型的就是Spring Bean。默认情况下,Spring Bean是单例的,因为Spring Bean的成员变量一般都是另外一些Spring Bean或者一些不可变配置,所以不管多少线程同时执行Spring Bean,都不会改变Spring Bean的状态。这样的设计非常节省内存。
但是如果执行结果必须要保存状态(一般是给别的线程种类消费),就该设计成第一种情况。因为这种设计可能会产生过多对象,一定要管理好这些对象的生命周期,防止内存溢出。比如,不要让它们在使用完了之后还一直被引用。
并发还容易产生过多的线程。这可以用线程池来解决。关于线程池会在别的专题讲。
Java并行还有一种特殊需求模型,那就是分支/合并模型。Java 7引入了fork-join框架处理这种模型。fork-join框架是一种将大任务动态拆分成小任务,小任务还可以继续拆分成更小的任务,最后把结果汇总合并的模型。
fork-join框架主要类介绍:
- ForkJoinTask:ForkJoinTask提供在任务中执行fork()和join()操作的机制,通常情况下我们会扩展ForkJoinTask的两个子类来使用fork-join框架:
- RecursiveAction:适用于没有返回结果的任务;
- RecursiveTask :适用于有返回结果的任务。
- ForkJoinPool :ForkJoinTask可以用ForkJoinPool线程池执行,每个线程都有一个工作队列,而且是双端队列。当一个线程率先完成任务,就可以偷别的线程的任务。执行自己的任务总是从队列的头部获取新任务,偷别人的任务总是从队列的尾部获取新任务,可以一定程度上减少线程竞争。这个叫做工作窃取(work-stealing)算法。
package per.xyl.nio;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** *
* 测试类(测试机器cpu i5-4200M,双核四线程)
* * 获取每个页面数据的模拟速度是20-60ms,平均每个页面40ms,单线程执行大约需要67min
* * 可以把整个任务切割成N个子任务,然后合并各个子任务的统计数据
* 如果子任务数过大(极端情况N=10w),在线程池数量配置得当的情况下,也要7s以上。最佳子任务数大约在3000-30000之间
* * 线程池最佳线程数大约为3000-5000之间 * 在合适的子任务数和线程池大小的情况下,执行时间可以控制在2s以内。是单线程性能的2000倍以上。
* * @author xiaoyilin *
* */
public class PageHandlerInvoker {
//配置要处理的页面数
private static final int PAGE_COUNT = 100000;
//配置切割的任务数
private static final int WORK_COUNT = 30000;
//配置线程池大小
private static final int THREAD_POOL_SIZE = 3000;
public static void main(String[] args) {
//记录执行开始时间
long startTime = new Date().getTime();
//每个小任务要执行的页面数
int pageCountPerWork = (PAGE_COUNT / WORK_COUNT) + 1;
//确保所有工作都已完成,然后进行统计工作
final CountDownLatch doneLatch = new CountDownLatch(WORK_COUNT);
//线程池,避免创建过多线程
ExecutorService excutorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
//工作线程类集合,做统计用
PageHandlerThread[] handlers = new PageHandlerThread[WORK_COUNT];
for (int i = 0; i < WORK_COUNT; i++) {
if ((pageCountPerWork * i) >= PAGE_COUNT) {
//空任务,如果所有页面都处理完了,还剩下一些任务,就走这个空任务
new Thread(String.valueOf(i)) {
@Override
public void run() {
doneLatch.countDown();
}
}.start();
} else if ((pageCountPerWork * (i + 1)) >= PAGE_COUNT) {
//最后一个任务,任务数没有其他任务多
PageHandlerThread handler = new PageHandlerThread(pageCountPerWork * i,
PAGE_COUNT, doneLatch);
excutorService.execute(handler);
handlers[i] = handler;
}
else {
//给每个任务分配相同的页面数
PageHandlerThread handler = new PageHandlerThread(pageCountPerWork * i,
pageCountPerWork * (i + 1), doneLatch);
excutorService.execute(handler);
handlers[i] = handler;
}
}
try {
doneLatch.await();
//等待所有任务执行完毕,再往下执行
}catch (InterruptedException e) {
//省略异常处理
}
//聚合所有任务的统计数据
int count = 0;
for (int i = 0; i < WORK_COUNT; i++) {
if (handlers[i] != null) {
count += handlers[i].getCount();
}
}
//打印出结果
System.out.println("偶数页面总数:" + count + " ,执行时间:" +
(new Date().getTime() - startTime) + "ms");
//关闭线程池
excutorService.shutdown();
}
}
/**
* * 页面处理工作线程类
* * @author xiaoyilin *
* */
class PageHandlerThread implements Runnable {
private final CountDownLatch doneLatch;
private final int start;
private final int end;
private int count = 0;
public PageHandlerThread(int start, int end, CountDownLatch doneLatch) {
this.start = start;
this.end = end;
this.doneLatch = doneLatch;
}
@Override
public void run() {
count = PageHandler.countEvens(start, end);
doneLatch.countDown();
}
public int getCount() {
return count;
}
}
/**
* * 页面处理工具类
* * @author xiaoyilin *
* */
class PageHandler {
//每个线程拥有一个单独的页面处理对象
private static final ThreadLocal<ThreadSpecificPageHandler> tl = new ThreadLocal<ThreadSpecificPageHandler>();
/** * 委托当前线程的页面处理对象,处理新的任务,返回当前线程处理的偶数页面总数 * 当前线程可能执行过多次任务 * @param start * @param end * @return */
public static int countEvens(int start, int end) {
return getThreadSpecificPageHandler(start, end).countEvens();
}
/**
* * 获取当前线程的页面处理对象,并给这个对象设置最新的任务
* * @param start
* * @param end
* * @return
* */
private static ThreadSpecificPageHandler getThreadSpecificPageHandler(
int start, int end) {
ThreadSpecificPageHandler tsph = tl.get();
if (tsph == null) {
tsph = new ThreadSpecificPageHandler();
}
tsph.setStart(start);
tsph.setEnd(end);
tl.set(tsph);
return tsph;
}
}
/**
* * 线程隔离的页面处理类
* * @author xiaoyilin
* * */
class ThreadSpecificPageHandler {
private int start;
private int end;
private int count = 0;
/**
* * 统计当前线程获取到的偶数页面数
* * @return
* */
public int countEvens() {
for (int i = start; i < end; i++) {
if (i == start) {
count = 0;
//如果这个线程执行过其他任务,就要清理掉以前的统计数据
}
count += getPageResultMock(i);
}
return count;
}
/**
* * 模拟获取页面内容是奇数还是偶数的方法,如果是偶数返回1,奇数返回0。
* * 休眠时间20ms-60ms,由一个随机数控制,模拟获取页面的时间;用一个四位随机数模拟页面内容。
* * @param i
* * @return
* */
private int getPageResultMock(int i) {
long startTime = new Date().getTime();
try {
Random randTime = new Random();
int randTimeInt = randTime.nextInt(60 - 20 + 1) + 20;
Thread.sleep(randTimeInt);
System.out.println(Thread.currentThread().getId()+"休眠时间:"+(randTimeInt));
}catch (InterruptedException e) {
//省略异常处理
}
Random rand = new Random();
int randomInt = rand.nextInt(9999 - 1000 + 1) + 1000;
int flag = 0;
if ((randomInt % 2) == 0) {
flag = 1;
}else {
flag = 0;
}
System.out.println(Thread.currentThread().getId()+"单个执行时间:"+(new Date().getTime() - startTime));
return flag;
}
public void setStart(int start) {
this.start = start;
}
public void setEnd(int end) {
this.end = end;
}
}
package per.xyl.nio;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* 启动类,用ForkJoinPool线程池执行
* @author XIAOYILIN
*
*/
public class PageHandlerForkJoinInvoker {
//配置要处理的页面数
private static final int PAGE_COUNT = 100000;
public static void main(String[] args) {
//记录执行开始时间
long startTime = new Date().getTime();
ForkJoinPool pool = (ForkJoinPool) Executors.newWorkStealingPool(1000);
//new Thread(new ThreadCount(pool)).start();
Integer result = pool.invoke(new ForkJoinPageHandler(0,PAGE_COUNT));
System.out.println(result);
//打印出结果
System.out.println("偶数页面总数:" + result + " ,执行时间:" +
(new Date().getTime() - startTime) + "ms");
}
}
/**
* 监控线程池活动线程数量
* @author XIAOYILIN
*
*/
class ThreadCount implements Runnable {
private ForkJoinPool pool;
public ThreadCount(ForkJoinPool pool) {
this.pool = pool;
}
@Override
public void run() {
while(true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println(pool.getActiveThreadCount());
}
}
}
/**
*
* @author XIAOYILIN
*
*/
class ForkJoinPageHandler extends RecursiveTask<Integer> {
private static final long serialVersionUID = 6729677280054383408L;
private int start;
private int end;
public ForkJoinPageHandler(int start,int end) {
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
if(end==start) {
return getPageResultMock(end);
}
ForkJoinPageHandler f1 = new ForkJoinPageHandler(start,(start+end)/2);
f1.fork();
ForkJoinPageHandler f2 = new ForkJoinPageHandler((start+end)/2+1,end);
return f2.compute() + f1.join();
}
private int getPageResultMock(int i) {
long startTime = new Date().getTime();
// try {
// Random randTime = new Random();
// int randTimeInt = randTime.nextInt(60 - 20 + 1) + 20;
// Thread.sleep(randTimeInt);
// System.out.println(Thread.currentThread().getId()+"休眠时间:"+(randTimeInt));
// }catch (InterruptedException e) {
// //省略异常处理
// }
Random rand = new Random();
int randomInt = rand.nextInt(9999 - 1000 + 1) + 1000;
int flag = 0;
if ((randomInt % 2) == 0) {
flag = 1;
}else {
flag = 0;
}
System.out.println(Thread.currentThread().getId()+"单个执行时间:"+(new Date().getTime() - startTime));
return flag;
}
}