Java线程之并行

Java并行无处不在。一般分为两种情况:

  1. 每个线程都在独立的状态环境下运行,说通俗一点就是,每个线程对应一套不同的Java对象;
  2. 所有线程都在一个无状态或状态不可变的环境下运行。

显然,第一种情况更占内存。如果可以设计成无状态或状态不可变环境,就尽量这么设计。最典型的就是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;
    }
	
}

猜你喜欢

转载自my.oschina.net/leaforbook/blog/1824734
今日推荐