Java串行/并行任务实现

        基础场景如下:用户在银行办理银行卡,银行卡办理完成后,可基于该卡办理会员服务,也可以基于该卡申请信用卡,最终通知用户全部办理完成。根据场景分析得知以下的流程图,办理银行卡必须串行执行,而后两个可以并行执行。

         先来模拟一下业务代码,三个方法分别代表办理银行卡、办理会员、申请信用卡,其中办理银行卡为了模拟较长的响应时间设置延迟3秒,其他方法延迟1秒。

public BankCardInfo applyBankCard(BankCardInfo info) {
    // TODO 设置银行名+密码,随机生成银行卡号并返回完整信息
    String bankCardNum = UUID.randomUUID().toString().replace("-", "");
    info.setBankCardNum(bankCardNum);

    try {
        // 模拟延迟3秒
        Thread.sleep(3000);
    } catch (Exception e) {
        e.printStackTrace();
    }
    log.info("银行卡申请成功!");
    return info;
}

public String registerUser(BankCardInfo cardInfo) {
    try {
        // 模拟延迟1秒
        Thread.sleep(1000);
    } catch (Exception e) {
        e.printStackTrace();
    }

    if (Objects.isNull(cardInfo.getBankCardNum())) {
        log.warning("银行卡尚未申请成功,用户注册失败");
        return "error";
    }

    UserInfo userInfo = new UserInfo();
    userInfo.setAge(18);
    userInfo.setBankCardNum(cardInfo.getBankCardNum());
    userInfo.setPassword("a1b2c3d4");
    userInfo.setUserName("user1");
    userInfo.setSexual("man");

    log.info("用户注册成功!");

    return "ok";
}

public String applyCreditCard(BankCardInfo cardInfo) {
    try {
        // 模拟延迟1秒
        Thread.sleep(1000);
    } catch (Exception e) {
        e.printStackTrace();
    }

    if (Objects.isNull(cardInfo.getBankCardNum())) {
        log.warning("银行卡尚未申请成功,信用卡申请失败");
        return "error";
    }

    CreditCardInfo creditCardInfo = new CreditCardInfo();
    creditCardInfo.setBankCardNum(cardInfo.getBankCardNum());
    creditCardInfo.setMoneyLimit(10000.00);

    log.info("信用卡申请成功!");

    return "ok";
}

场景1 异步任务(无回调)

       这种情况针对主线程只完成最主要的任务,将其他任务提交给子线程后,就直接返回完成任务。这种方法最好是不要用在很核心的功能上,不然某个子线程异常回滚,其他线程又执行成功,你基本就可以去死了。

        想象一下兑换物品的场景,子线程1扣积分,子线程2换东西,刚好积分数据库炸了,物品数据库猛扣,一夜之间物品全部被0元购。但是一般重要的业务会走异步回调,也不用太操心。

        回到例子上,就让他办理银行卡成功后直接返回。 接下来编写执行全流程主方法,由于办理会员/申请信用卡依赖办理银行卡的返回值,所以给办理银行卡线程加上join(),在他执行完前阻塞其他线程。

public void applyMain() {
    BankCardController controller = new BankCardController();

    BankCardInfo cardInfo = new BankCardInfo("ChinaBank", null, 88888888, 0.00);
    Thread cardThread = new Thread(() -> applyBankCard(cardInfo));
    try {
        cardThread.start();
        cardThread.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    Thread userThread = new Thread(() -> registerUser(cardInfo));
    userThread.start();

    Thread creditThread = new Thread(() -> applyCreditCard(cardInfo));
    creditThread.start();

    log.info("执行完毕,通知用户");
}

        试着执行一下,日志信息如下。执行3秒后办理银行卡线程完成,主线程同时完成,1秒后另外两个子线程也执行完毕。这种场景其实比较简单随意,也不是很优雅,是直接阻塞线程;下一种场景我们采用更优雅的方式。

一月 31, 2023 4:22:20 下午 BankRegister.Controller.BankCardController applyBankCard
信息: 银行卡申请成功!
一月 31, 2023 4:22:20 下午 BankRegister.Controller.BankCardController applyMain
信息: 执行完毕,通知用户
一月 31, 2023 4:22:21 下午 BankRegister.Controller.BankCardController registerUser
信息: 用户注册成功!
一月 31, 2023 4:22:21 下午 BankRegister.Controller.BankCardController applyCreditCard
信息: 信用卡申请成功!

        补充:Thread的join()方法底层是调用了Native wait()方法,是让当前线程等待,“当前线程”的意思就是你所处的线程,在例子中为Main线程。因此对子线程调用join()为让主线程等待子线程执行完毕,也就达到了cardThread执行完毕再继续执行的效果。

        可以去SpringBoot中证实,在Service方法中执行Thread.currentThread.join(),当前线程毫无疑问是Main线程,被调用的线程也是Main线程,就陷入了死锁导致阻塞。

场景2 同步任务

        这种场景也是日常最常见的,所有任务成功执行完后通知用户。还是刚刚的业务方法,但这次要对注册用户方法进行一定的改进。

        在之前的方法中存在极大的缺陷,我们获取不到返回值,当返回error时仍然当做任务执行结束,从而继续向下执行。可以试验一下,在办理银行卡完成后手动将银行卡号置为null,尽管子线程都返回error,仍然会正常通知用户。很明显我们是需要返回值,且要依赖他来进行通知结果返回处理。

        都说到这份上,很明显要引入Callable和Future了。既然要做,我们就优雅到极致:

  1. 新增用户注册/申请信用卡的返回值判断,动态返回通知用户语句;
  2. 新增办理银行卡返回时间限制,超时任务取消;
  3. 新增用户注册中的“用户名重复”异常结果;
  4. 引入线程池。

        注册用户方法新增用户名重复判断,现在的逻辑为:输入用户名“admin”时,会直接返回error。

public String registerUser(BankCardInfo cardInfo, String userName) {
    try {
        // 模拟延迟1秒
        Thread.sleep(1000);
    } catch (Exception e) {
        e.printStackTrace();
    }

    if (Objects.isNull(cardInfo.getBankCardNum())) {
        log.warning("银行卡尚未申请成功,用户注册失败");
        return "error";
    }

    if ("admin".equals(userName)) {
        log.warning("该用户名已存在,用户注册失败");
        return "error";
    }

    UserInfo userInfo = new UserInfo();
    userInfo.setAge(18);
    userInfo.setBankCardNum(cardInfo.getBankCardNum());
    userInfo.setPassword("a1b2c3d4");
    userInfo.setUserName(userName);
    userInfo.setSexual("man");

    log.info("用户注册成功!");
    return "ok";
}

         主方法中引入了线程池,将3个工作线程修改为Callable形式并提交给线程池;为办理银行卡设置了5000ms的超时时间,增加了超时手动取消任务逻辑;在最后新增了对两个子线程返回值的判断,动态展示通知信息。

//线程池
private static final ExecutorService executor = Executors.newFixedThreadPool(5);

public void applyMain2() {
    BankCardController controller = new BankCardController();

    BankCardInfo cardInfo = new BankCardInfo("ChinaBank", null, 88888888, 0.00);
    Callable<BankCardInfo> cardCallable = () -> applyBankCard(cardInfo);
    Callable<String> registerCallable = () -> registerUser(cardInfo, "admin");
    Callable<String> applyCreditCallable = () -> applyCreditCard(cardInfo);

    Future<BankCardInfo> submit1 = executor.submit(cardCallable);
    try {
        BankCardInfo cardInfo1 = submit1.get(5000, TimeUnit.MILLISECONDS);
        log.info(cardInfo1.toString());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (TimeoutException e) {
        e.printStackTrace();
        log.info("任务取消");
        submit1.cancel(true);
    }

    String res1 = null;
    String res2 = null;
    Future<String> submit2 = executor.submit(registerCallable);
    Future<String> submit3 = executor.submit(applyCreditCallable);
    try {
        res1 = submit2.get();
        res2 = submit3.get();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }

    if ("ok".equals(res1) && "ok".equals(res2)) {
        log.info("执行完毕,通知用户成功");
    } else {
        log.info("执行异常,通知用户失败");
    }
}

         由于我们将用户名传入了admin,返回的通知也成为了失败通知。在调小get()的超时时间后,线程超时捕获到TimeoutException,也成功手动取消了任务。

一月 31, 2023 8:02:10 下午 BankRegister.Controller.BankCardController applyBankCard
信息: 银行卡申请成功!
一月 31, 2023 8:02:10 下午 BankRegister.Controller.BankCardController registerUser
警告: 该用户名已存在,用户注册失败
一月 31, 2023 8:02:11 下午 BankRegister.Controller.BankCardController applyCreditCard
信息: 信用卡申请成功!
一月 31, 2023 8:02:11 下午 BankRegister.Controller.BankCardController applyMain2
信息: 执行异常,通知用户失败

        但是仔细看一下执行时间就会发现还是有问题,办理会员和申请信用卡相差一秒,明显是串行了,因为get()方法也把他们俩阻塞住了。有一个不太合适的优化方法,就是循环调用Future的isDone()判断两个线程是否都执行结束,结束后再获取返回值。后面想办法会再优化这块(CompletableFuture或CountDownLatch),先实现一下。

        try {
            while(true) {
                if (submit2.isDone() && submit3.isDone()) {
                    res1 = submit2.get();
                    res2 = submit3.get();
                    break;
                } else {
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        如上面代码的逻辑,两线程状态都为完成后再获取返回值并跳出循环即可。

场景3 同步任务(优化)

        场景2最后我们使用了一个不太优雅的方法,无限循环判断Future的isDone()状态,这对CPU的消耗是非常大的,需要优化,因此引入CountDownLatch。

        他是一个多线程任务计数器,初始化一个数值并在子线程执行结束时手动-1,主线程中调用await()方法就可以在计数器清零前一直阻塞主线程执行。用法也很简单,只需要给每个子线程都传入该计数器,并在finally中显式调用countDown()使其减一即可。

        try {
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //手动减一
            countDownLatch.countDown();
        }

        那么那段while(true)的丑陋循环就可以优化成,依据CountDownLatch的值是否归零,判断两个子线程执行完毕与否,完成再获取值来执行判断逻辑。

try {
    //await()方法会在计数器清零前,一直阻塞主线程,直至子线程全部执行完毕
    countDownLatch.await();
    res1 = submit2.get();
    res2 = submit3.get();
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException ex) {
    ex.printStackTrace();
}

        基础的同步异步实现到这里就介绍完了,主要就是依靠FutureTask + Callable + CountDownLatch来实现。在SpringBoot中有更加方便的实现,但同时也会出现许多问题,因此放在下一篇中介绍。

Java可以使用多线程实现矩阵的并行计算,从而提高计算效率。以下是串行和并行求矩阵的Java实现示例: 1. 串行求矩阵 ```java public class SerialMatrix { public static int[][] multiply(int[][] A, int[][] B) { int m = A.length, n = A[0].length, p = B[0].length; int[][] C = new int[m][p]; for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { for (int k = 0; k < n; k++) { C[i][j] += A[i][k] * B[k][j]; } } } return C; } } ``` 2. 并行求矩阵 ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class ParallelMatrix { public static int[][] multiply(int[][] A, int[][] B) throws InterruptedException { int m = A.length, n = A[0].length, p = B[0].length; int[][] C = new int[m][p]; int processors = Runtime.getRuntime().availableProcessors(); ExecutorService executor = Executors.newFixedThreadPool(processors); for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { executor.submit(new Task(i, j, A, B, C)); } } executor.shutdown(); executor.awaitTermination(1, TimeUnit.HOURS); return C; } private static class Task implements Runnable { int i, j; int[][] A, B, C; public Task(int i, int j, int[][] A, int[][] B, int[][] C) { this.i = i; this.j = j; this.A = A; this.B = B; this.C = C; } @Override public void run() { int n = A[0].length; for (int k = 0; k < n; k++) { C[i][j] += A[i][k] * B[k][j]; } } } } ``` 上述并行求矩阵的实现使用了Java的线程池和多线程技术,将矩阵的计算任务分配给多个线程并行执行,从而提高了计算效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值