Java并发编程——分治编程

分治算法是五大常用算法之一,本来不应该在这个时间写这篇博客,因为之前的线程池还没有写完,有些知识点也是需要用到的线程池的,但是架不住现在的项目里有个坑队友,名曰大乒乓,他好像是批量注册用户还是什么(暂时就先当做是批量注册吧~),反正就是很多很多用户,每个用户大概0.5秒左右的注册时间,要睡觉时我问他,你的电脑怎么还开着啊,他说在那注册用户呢,得跑一宿···几千条还是几万条,就一个主线程for循环在那跑着······所以先写个博客,也算是给他写的,看看用分治算法怎么来解决这个问题。

一、Fork/Join框架分治算法的概念:

直接百度上搜的图片:
流程图

Fork/Join框架直白点说,就是把一个大任务分解成若干个小任务,小任务可以继续无限的分解成小小任务、小小小任务,每个子线程处理这些分解后的任务,然后将处理完成后的结果合并,最后合并成一个完整的处理结果,然后返回结果。

二、Fork/Join框架简介

首先我们需要一个处理任务的线程池:

ForkJoinPool pool = new ForkJoinPool();

线程池可以执行一个简单的任务:

        pool.submit(Callable);
        pool.submit(Runnable);
        pool.submit(ForkJoinTask);
        pool.execute(Runnable);
        pool.execute(ForkJoinTask);

因为线程池还没有写完,所以execute方法和submit方法的区别,大家有兴趣可以先在网上搜索下,我这里简单说下:
区别1:submit方法执行后有返回值,返回任务的结果,而execute方法没有返回值。
区别2(延伸):submit提交的任务在线程池ThreadPoolExecutor中是不可以remove掉的,而execute提交的任务可移除(ForkJoinPool 没有reove方法)。
区别3:其他(比如捕获异常什么的,大家自行搜索吧)

我们现在只看 pool.submit(ForkJoinTask)和pool.execute(ForkJoinTask),他们都接收一个ForkJoinTask任务,我们来看下ForkJoinTask:
这里写图片描述
我们可以看到,ForkJoinTask是继承Object类的,下面还有好好多多子类,我们接着看他的子类:RecursiveTask和RecursiveAction,这也是今天我们要用到的两个任务类。

三、用小例子讲解RecursiveAction的用法

直接上一个小demo:

public class TestTask extends RecursiveAction {

    private int start;
    private int end;
    /**
     * 分解成小任务的最小数量,也就是说,如果数量小于这个值,就不用再分解了
     */
    private static final int MAX = 5;

    public TestTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected void compute() {
        //判断当前数量和MAX做对比,如果大于MAX,分解成小任务
        if (end - start > MAX) {
            int num = (start + end) / 2;
            //把当前的大任务TestTask分解成两个新的小任务testTask1和testTask2
            TestTask testTask1 = new TestTask(start, num);
            TestTask testTask2 = new TestTask(num + 1, end);
            //fork方法,分解形成小任务并执行
            testTask1.fork();
            testTask2.fork();
        } else {
            //如果满足小任务的数量(没超过MAX)
            for (int i = start; i <= end; i++) {
                System.out.println(i);
            }
        }
    }
}

这里可能需要讲解一下,然后就直接写在注释里面了。这里值得注意一下的就是fork()方法,这是核心方法。运行代码:

    public static void main(String[] args) {
        TestTask task = new TestTask(0, 20);
        ForkJoinPool pool = new ForkJoinPool();
        pool.submit(task);
    }

看下log:

12-27 13:50:55.540 5068-8650/lbx.myapplication I/System.out: 16
12-27 13:50:55.540 5068-8650/lbx.myapplication I/System.out: 17
12-27 13:50:55.540 5068-8650/lbx.myapplication I/System.out: 18
12-27 13:50:55.540 5068-8650/lbx.myapplication I/System.out: 19
12-27 13:50:55.540 5068-8650/lbx.myapplication I/System.out: 20
12-27 13:50:55.540 5068-8652/lbx.myapplication I/System.out: 11
12-27 13:50:55.540 5068-8652/lbx.myapplication I/System.out: 12
12-27 13:50:55.540 5068-8652/lbx.myapplication I/System.out: 13
12-27 13:50:55.540 5068-8652/lbx.myapplication I/System.out: 14
12-27 13:50:55.540 5068-8652/lbx.myapplication I/System.out: 15
12-27 13:50:55.540 5068-8651/lbx.myapplication I/System.out: 6
12-27 13:50:55.540 5068-8651/lbx.myapplication I/System.out: 7
12-27 13:50:55.540 5068-8651/lbx.myapplication I/System.out: 8
12-27 13:50:55.540 5068-8651/lbx.myapplication I/System.out: 9
12-27 13:50:55.540 5068-8651/lbx.myapplication I/System.out: 10
12-27 13:50:55.540 5068-8652/lbx.myapplication I/System.out: 0
12-27 13:50:55.540 5068-8652/lbx.myapplication I/System.out: 1
12-27 13:50:55.540 5068-8652/lbx.myapplication I/System.out: 2
12-27 13:50:55.540 5068-8652/lbx.myapplication I/System.out: 3
12-27 13:50:55.540 5068-8652/lbx.myapplication I/System.out: 4
12-27 13:50:55.540 5068-8652/lbx.myapplication I/System.out: 5

四、RecursiveTas的用法,用RecursiveTas实现查找数组最大数的功能

RecursiveTas和RecursiveAction的区别就是, RecursiveTas需要传入一个泛型,重写compute()方法后,返回值是这个泛型。RecursiveAction的compute()方法返回值是void。
看下栗子:

public class MaxTask extends RecursiveTask<Integer> {

    private int[] ints;
    /**
     * 两个两个作比较
     */
    public static final int MAX = 2;

    public MaxTask(int[] ints) {
        this.ints = ints;
    }

    @Override
    protected Integer compute() {
        int length = ints.length;
        if (length > MAX) {
            int num = length / 2;
            int[] ints1 = new int[num];
            int[] ints2 = new int[length - num];
            //拷贝数组
            System.arraycopy(ints, 0, ints1, 0, ints1.length);
            System.arraycopy(ints, num, ints2, 0, ints2.length);
            MaxTask testTask1 = new MaxTask(ints1);
            MaxTask testTask2 = new MaxTask(ints2);
            ForkJoinTask<Integer> fork1 = testTask1.fork();
            ForkJoinTask<Integer> fork2 = testTask2.fork();
            try {
                return Math.max(fork1.get(), fork2.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        } else {
            //有的时候传进来的数组是单数,所以从“两两比较”变成“只有一个数的数组”
            return ints.length == 1 ? ints[0] : Math.max(ints[0], ints[1]);
        }
        return -1;
    }
}

这里有两行代码:System.arraycopy(),这个方法的作用是拷贝数组,不会用的童鞋去网上搜索下,篇幅有限就不在这讲解了。get()方法会在下一段代码后的文字中说明。
调用代码:

    public static void main(String[] args) {
        int[] ints = new int[]{0, -1, -2, 100, 98, 7, 0};
        MaxTask task = new MaxTask(ints);
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Integer> submit = pool.submit(task);
        try {
            Integer integer = submit.get();
            System.out.println("最大值:" + integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

接下来看log:
注意下这行代码:Integer integer = submit.get(); get方法具有阻塞效果,也就是说,如果任务没有发完成的时候,会在get()这里阻塞住,直到任务完成,才会释放阻塞并返回结果,若已经完成任务,那么get()方法直接回返回结果。

12-27 14:18:08.600 28736-28736/lbx.myapplication I/System.out: 最大值:100

五、实战!用RecursiveTas解决坑队友的注册问题!注意,这不是单线程!不是单线程!

首先,我们只知道用户的昵称,但是没有账号密码,需要注册,新建一个用户实体类:

public class AccountBean {
    public String account;
    public String password;
    public String nickName;

    public AccountBean(String nickName) {
        this.nickName = nickName;
    }

    @Override
    public String toString() {
        return "AccountBean{" +
                "account='" + account + '\'' +
                ", password='" + password + '\'' +
                ", nickName='" + nickName + '\'' +
                '}';
    }
}

然后创建一个Task:

public class SignUpTask extends RecursiveTask<List<AccountBean>> {

    private List<AccountBean> list;
    /**
     * 每个小任务注册10个用户
     */
    private static final int MAX = 10;

    public SignUpTask(List<AccountBean> list) {
        this.list = list;
    }

    @Override
    protected List<AccountBean> compute() {
        int size = list.size();
        if (size > MAX) {
            //当前的任务注册数大于MAX 10 ,所以要分解成两个小任务
            int num = size / 2;
            //小任务1的注册用户集合
            List<AccountBean> list1 = list.subList(0, num);
            //小任务2的注册用户集合
            List<AccountBean> list2 = list.subList(num + 1, list.size());
            //小任务1
            SignUpTask signUpTask1 = new SignUpTask(list1);
            //小任务2
            SignUpTask signUpTask2 = new SignUpTask(list2);
            //执行小任务1
            ForkJoinTask<List<AccountBean>> fork1 = signUpTask1.fork();
            //执行小任务2
            ForkJoinTask<List<AccountBean>> fork2 = signUpTask2.fork();
            try {
                List<AccountBean> forkList = new ArrayList<>();
                //获取小任务1的结果  注意get()方法有阻塞效果
                List<AccountBean> bean1 = fork1.get();
                //获取小任务2的结果  注意get()方法有阻塞效果
                List<AccountBean> bean2 = fork2.get();
                forkList.addAll(bean1);
                forkList.addAll(bean2);
                //把小任务1和小任务2的结果放到一个集合里,并返回结果
                return forkList;
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        } else {
            //分割成的最小任务,注册用户数量已经小于MAX 10了
            List<AccountBean> signUpList = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                AccountBean bean = list.get(i);
                //注册
                AccountBean signUpBean = singUp(bean);
                signUpList.add(signUpBean);
            }
            return signUpList;
        }
        return null;
    }

    /**
     * 注册
     *
     * @param bean 未注册人员的实体类
     * @return 注册完人员的实体类,里面多了注册后的账号、密码
     */
    private AccountBean singUp(AccountBean bean) {
        try {
            //模拟注册的0.5秒耗时
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        bean.account = bean.nickName + "123";
        bean.password = bean.nickName + "456";
        return bean;
    }

}

注解说的尽量详细了,这要是没看懂,那肯定是没认真看,还是从头再看一遍吧···

接下来运行代码:

public static void main(String[] args) {
        List<AccountBean> list = new ArrayList<>();
        list.add(new AccountBean("张三"));
        list.add(new AccountBean("李四"));
        list.add(new AccountBean("王五"));
        list.add(new AccountBean("赵六"));
        for (int i = 0; i < 10; i++) {
            list.add(new AccountBean("赵" + i + "号"));
        }
//----------------------以上是创建待注册用户--------------------
        ForkJoinPool pool = new ForkJoinPool();
        SignUpTask signUpTask = new SignUpTask( list);
        ForkJoinTask<List<AccountBean>> task = pool.submit(signUpTask);
        try {
            //get有阻塞作用,再三强调
            List<AccountBean> beanList = task.get();
            for (AccountBean bean : beanList) {
                //循环打印出注册后的用户信息
                System.out.println("AccountBean = " + bean.toString());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

看下log:

12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='张三123', password='张三456', nickName='张三'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='李四123', password='李四456', nickName='李四'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='王五123', password='王五456', nickName='王五'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='赵六123', password='赵六456', nickName='赵六'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='赵0号123', password='赵0号456', nickName='赵0号'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='赵1号123', password='赵1号456', nickName='赵1号'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='赵2号123', password='赵2号456', nickName='赵2号'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='赵4号123', password='赵4号456', nickName='赵4号'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='赵5号123', password='赵5号456', nickName='赵5号'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='赵6号123', password='赵6号456', nickName='赵6号'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='赵7号123', password='赵7号456', nickName='赵7号'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='赵8号123', password='赵8号456', nickName='赵8号'}
12-27 14:32:46.670 26082-26082/lbx.myapplication I/System.out: AccountBean = AccountBean{account='赵9号123', password='赵9号456', nickName='赵9号'}

我们发现,所有的用户都注册完成了。多线程真的是太重要了,单线程的效率太低。

六、常用方法:

//获取返回值,捕获异常
Boolean aBoolean = task.get();
//获取返回值,直接抛出异常,同步
Boolean aBoolean = task.join();
//execute处理返回值
MyTask task = new MyTask();
pool.execute(task);
task.get();

//执行多个任务并返回
List<Future<Boolean>> futures = pool.invokeAll();
//执行多个任务并返回第一个完成的任务
Future<Boolean> future = pool.invokeAny();
//3秒内线程池是否被shutdown
boolean b = pool.awaitTermination(3, TimeUnit.SECONDS);

//直接返回结果,不用get取值
Boolean b = pool.invoke(task);

//获得并行的数量,与CPU内核数有关
pool.getParallelism();
//获得任务池的大小
pool.getPoolSize();
//获取已提交但未执行的任务的个数
int count = pool.getQueuedSubmissionCount();
//队列中是否有未执行的任务
boolean b = pool.hasQueuedSubmissions();
//获得任务的总个数
long taskCount = pool.getQueuedTaskCount();
//获取偷窃的任务个数
long stealCount = pool.getStealCount();
//获取正在执行并且没有阻塞的线程个数
int runningThreadCount = pool.getRunningThreadCount();
//线程池是否处于静止的没任务状态
boolean quiescent = pool.isQuiescent();

再次声明一下,这篇博客写的比较早,有些知识点在我之前的博客里并没有讲到,比如invokeAll,invoke,Future、Callable等,这里只是适当的点到了一下,后期我会在博客:线程池专题中一 一讲出每个方法的用法,所以说,坑队友,就是这么坑!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值