Java异步线程事务回滚问题

前言 :

假设有一个很大的任务。这个任务可以分成 a 、 b 、 c三个步骤同时进行,

在同时进行的情况下,如果有一个任务被取消掉了或者遇到错误了,然后所有的都应该结束,要求高效完成,主要是高效。

严格来讲这是一个分布式事务的问题,也就是说有一个事务,此事务的某一段放到a级上去执行,其余两段分别放到b和c,其中有一个半截单子的子事务没有完成,那整个事务取消掉。

面试题

 关于线程同步的面试题,凡是从时间角度或者是优先级角度考虑解决思路的,基本全不对!凡是从join sleep考虑的,99.99%的不对,线程优雅的结束,一般不用interrupt stop resume,不得已再使用interrupt。

经典面试题:synchronized和ReentrantLock的区别是什么?

答案:ReentrantLock可以多个队列,synchronized只有一个队列,ReentrantLock可以做公平锁,synchronized只有非公平(没有公平可言),ReentrantLock还可以tryLock尝试上锁,上不了锁可以去做别的(做出一定的处理),但是synchronized只能上来就死等傻傻等待,等不到就死在那,第四个区别:锁可以被打断,就是我在锁的过程中我可以让别人打断我,打折我的腿我就醒了这个叫lock.incorruptibly可以做这个操作就是entry.lock,但是synchronized不可以,因为synchronized不能打断,除非把整个线程给砍掉。

面试题:【面试过程中现场实现】


提示以下是本篇文章正文内容,下面案例可供参考

举例:张三李四一行人现在要吃火锅要有很多步骤

  • 张三要去用锅烧开水
  • 李四要准备买菜洗菜
  • 路人王要去请王二来一起吃

 假设现在张三去起锅烧水,李四去买菜了,路人王要去请王二来一起吃。

然后其中路人王在去请王二的路上遇到了pk无法去请王二来吃火锅,这个时候应该考虑什么,整个任务全取消,即便是张三在烧水,李四在买菜也取消,停掉。

 示例方案一(存在问题):

可以翻看下面的几个案例有一个循序渐进的过程。

package com.aaa.blbl_Test2;

import java.io.IOException;

public class T0_F1 {
    public static void main(String[] args) {
        /*假设我们有三个任务,t1、t2、t3,然后假设每一个任务正常执行完结束之后,分别应该是3秒钟、
         1秒钟、五秒钟,当然为了模拟这个任务t1、t2的两个任务都能正常执行完,所以后面传true的意思是说
        让它正常执行完,穿false的意思是说,让它执行到一秒的时候报个错
         因为是模拟任务,所以在这里直接给了线程的执行时间,1000毫秒=1秒*/
        Thread t1 = new MyTask("t1",3000,true);
        Thread t2 = new MyTask("t2",1000,false);
        Thread t3 = new MyTask("t3",5000,true);
        //演示回滚过程
        t1.start();
        t2.start();
        t3.start();
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static class MyTask extends Thread
    {
        private String name;//名字
        private int timeInSeconds;//执行时间
        private boolean success;//执行成功还是失败

        public MyTask(String name, int timeInSeconds, boolean success) {
            this.name = name;
            this.timeInSeconds = timeInSeconds;
            this.success = success;
        }
        @Override
        public void run()
        {
            //模拟业务执行时间
            //实际中时间不固定,可能在处理计算任务,或者是等待Io任务
            try {
                sleep(timeInSeconds);
                System.out.println(name+"任务结束!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

存在问题:因为程序正常的结束取决于你最长的这个时间段,比如t1执行时间是十秒sleep(10000)我们整个程序要想结束,别的线程就要等它十秒才能结束。

但是按照题目的要求一秒就应该结束,因为一秒钟有一个任务已经报错了,所以一秒就应该结束。

示例方案二:(小白玩法)

我们给每一个任务,给它指定一个执行的结果,注意看第二个版本的玩法,这里是分成一个一个版本来实现的,如果不分版本的话对于基础稍弱的很可能写不到最后的那个版本。

package com.aaa.blbl_Test2;

import java.util.ArrayList;
import java.util.List;

public class T0_F2 {
    public static void main(String[] args) {
        /*版本二玩法:
        需要有某一个东西来告知这个线程是正常成功了
        还是没成功他的状态还是说没结束呢?
        所以在下面做了一个枚举类型,这个枚举类型主要,指带的每一个线程它的状态,
        一共有三个状态。
        第一个状态是NOTEND也就是说现在正在运行之中。
        第二个状态:SUCCESSED代表正常运行结束。
        第三个状态:FAILED代表运行失败。
        这样做的需求,因为需要知道这个线程他执行失败了,需要知道他的状态。
        如果执行失败了通知所有的线程结束。
        实现步骤:
         private static enum Result{
            NOTEND,SUCCESSED,FAILED
        }
        */
        Thread t1 = new T0_F2.MyTask("t1",3000,true);
        Thread t2 = new T0_F2.MyTask("t2",1000,false);
        Thread t3 = new T0_F2.MyTask("t3",5000,true);

        List<MyTask> tasks = new ArrayList<>();
        tasks.add((MyTask) t1);
        tasks.add((MyTask) t2);
        tasks.add((MyTask) t3);


        //启动线程
        tasks.stream().forEach((t -> t.start()));
        /*
        接上面:实现步骤:首先把所有的线程全装到一个list里,然后让所有的线程启动,
        启动完成之后,就启动一个监视程序也就是下面的for循环,一直不停的检测它。
         */

        //启动监视
        for (; ; ) {
            for (MyTask task : tasks)
            {
                // 如果有某一个线程getResult()它的Result是FAILED的了整个线程失败、System.exit(0)整个程序结束。
                if (task.getResult() == Result.FAILED)
                {
                    System.exit(0);
                }
            }
        }
    }
    private static enum Result{
        NOTEND,SUCCESSED,FAILED
    }
    private static  class  MyTask extends Thread
    {
        private Result result = Result.NOTEND;
        private String name;
        private int timeInSeconds;
        private boolean success;

        public MyTask(String name, int timeInSeconds, boolean success) {
            this.name = name;
            this.timeInSeconds = timeInSeconds;
            this.success = success;
        }

        public Result getResult() {
            return result;
        }

        @Override
        public void run()
        {
            //模拟业务执行时间
            //实际中时间不固定,可能在处理计算任务,或者是等待Io任务
            try {
                sleep(timeInSeconds);

                System.out.println(name+"任务结束!");

                result = success ? Result.SUCCESSED : Result.FAILED;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这个方法也是绝对不能够被允许的:

接上图它里面的for循环是一个盲等待,盲等待(就是一直不停循环代表不停的消耗cpu,

是效率上的降低,不能够被允许,第二点:System.exit(0):粗暴的干掉一切线程,

假设路人王去请王二来吃火锅,一秒钟之后失败了,李四在买菜的路上遇到了修路,张三在烧水把锅踢飞,完全有可能是不一致的状态,打开了半截文件直接就退出了,这个文件还没有close,建立了一个链接对方还在等着我说话,这边还没说直接就close了,所以该方案也是存在问题的不能被允许)

示例方案三:(循序渐进)

示例每一个线程在运行的时候,加一个最终的处理方法叫boss.end

package com.aaa.blbl_Test2;


/**
 * 最原始的方法 Thread run()重写
 */
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class T0_F3 {
    private static  class Boss extends Thread{

        private List<Worker> tasks = new ArrayList<>();

        public void addTask(Worker t)
        {
            tasks.add(t);
        }
        @Override
        public void run()
        {
            tasks.stream().forEach((t) -> t.start());
        }

        public void end(Worker worker)
        {
            if (worker.getResult()==Result.FAILED)
            {
                System.exit(0);
            }
        }
    }

    public static void main(String[] args) {
        /*
           Boss boss = new Boss();
           boos是最大管理员,每一个Worker都拥有一个boos的引用,
           如果某一个线程结束了,在boss.end(this)处理方法如果该线程Result.FAILED
           那么就整个退出 System.exit(0),这样解决了盲等待的问题。
         */
        Boss boss = new Boss();
        Worker t1 = new Worker(boss,"t1",3000,true);
        Worker t2 = new Worker(boss,"t2",1000,false);
        Worker t3 = new Worker(boss,"t3",5000,true);
        boss.addTask(t1);
        boss.addTask(t2);
        boss.addTask(t3);

        //启动线程

        boss.start();

        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static enum Result{
        NOTEND,SUCCESSED,FAILED
    }

    private static class Worker extends Thread
    {
        private Result result = Result.NOTEND;
        private Boss boss;
        private String name;
        private int timeInSeconds;
        private boolean success;

        public Worker(Boss boss, String name, int timeInSeconds, boolean success) {
            this.boss = boss;
            this.name = name;
            this.timeInSeconds = timeInSeconds;
            this.success = success;
        }

        public Result getResult() {
            return result;
        }

        @Override
        public void run()
        {
            //模拟业务执行时间
            //实际中时间不固定,可能在处理计算任务,或者是Io任务
            try {
                sleep(timeInSeconds);

                System.out.println(name+"任务结束!");

                result = success ? Result.SUCCESSED : Result.FAILED;

                boss.end(this);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
 

最终方案

书写回滚,回滚这个事件就是每种是每种不同的方式,就是每一个线程有每一个线程不同的回滚方式。

package com.aaa.blbl_Test2;


import java.util.ArrayList;
import java.util.List;

/**
 * 最原始的方法 Thread run()重写
 */
public class F0_F4 {
    private static  class Boss extends Thread{

        private List<Worker> tasks = new ArrayList<>();

        public void addTask(Worker t)
        {
            tasks.add(t);
        }
        @Override
        public void run()
        {
            tasks.stream().forEach((t) -> t.start());
        }

        public void end(Worker worker)
        {
            //当某一个线程结束了,并不是把其他线程的腿打断
            //而是要让它取消cancel
            if (worker.getResult() == Result.FAILED)
            {
               cancel(worker);
            }
        }
        private void cancel(Worker worker)
        {
            for (Worker w : tasks)
            {
                if (w != worker) w.cancel();
            }
        }
    }

    public static void main(String[] args) {

        Boss boss = new Boss();

        Worker t1 = new Worker(boss,"A===",3,true);
        Worker t2 = new Worker(boss,"B===",1,false);
        Worker t3 = new Worker(boss,"C===",5,true);

        boss.addTask(t1);
        boss.addTask(t2);
        boss.addTask(t3);

        //启动线程

        boss.start();

        try {
            System.in.read();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static enum Result{
        NOTSET,SUCCESSED,FAILED,CANCELLED
    }

    private static class Worker extends Thread
    {
        private Result result = Result.NOTSET;

        private Boss boss;
        private String name;
        private int timeInSeconds;
        private boolean success;

        private volatile  boolean cancelling = false;

        public Worker(Boss boss, String name, int timeInSeconds, boolean success) {
            this.boss = boss;
            this.name = name;
            this.timeInSeconds = timeInSeconds;
            this.success = success;
        }

        public Result getResult() {
            return result;
        }

        @Override
        public void run()
        {
            int interval = 100;
            int total = 0;

            for ( ; ; )
            {
                try {
                    sleep(interval);//cpu密集型
                    total += interval;
                    if (total / 1000  >= timeInSeconds)
                    {
//                        System.out.println(total+"$$$$");
//                        System.out.println(timeInSeconds);
                        System.out.println(name+"任务结束!"+result);//正常结束
                        result = success ? Result.SUCCESSED : Result.FAILED;
                        System.out.println("的撒大");
                        break;
                    }
                    if (cancelling)
                    {
                        rollback();
                        result = Result.CANCELLED;
                        cancelling = false;
                        System.out.println(name="执行完毕!"+result);
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           boss.end(this);
        }
        private void rollback()
        {
            try {
                //书写回滚
                System.out.println(name+"rollback start....");
               /* rollback事务回滚,不同的事务是不同的回滚
                举例:所有事件整体是一个大事务,它要求第一个事务是关于数据库的
                那么插入五条数据就要(cancel)删除五条数据、假设第二个事务是关于文件拷贝
                文件拷贝完之后就要进行删除、第三个是关于MQ里面往里面发消息的
                假设发进去几条消息,然后在发几条取消掉的消息这叫rollback
                如果在rollback过程中出错,假设插入五条数据删到三条的时候出错了
                举例:发出总任务的这个人一定有一个最大等待时间,
                假如你在特定的时间内没有完成,那就说明任务失败,那其中有一个子任务,
                那这个子任务,如果中间rollback失败了,那就是一定抛出异常了
                这个时候一定要记录日志,发告警必须人工处理了,那上面的数据的那个来说删不掉了
                说明里面有脏数据了,可能不是删除可能是修改,这时候必须人工处理了,把运维程序员从睡梦中叫醒来处理
                */
                sleep(500);
                System.out.println(name+"rollback end!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        public void cancel() {
            cancelling = true;//设立标志位
        }
    }

}



先进处理方案
package com.aaa.blbl_Test2;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * 问题:可以归分为分布式事务失败回滚的手工实现
 * 代码不完善,可扩展
 */
public class F0_F5 {
    //任务执行结束的三种状态
    private static  enum Result
    {
        SUCCESS,FAIL,CANCELLED
    }

    static List<MyTask> tasks = new ArrayList<>();

    public static void main(String[] args) {
        MyTask task1 = new MyTask("task1===",3,Result.SUCCESS);
        MyTask task2 = new MyTask("task2===",5,Result.SUCCESS);
        MyTask task3 = new MyTask("task3===",1,Result.FAIL);

        tasks.add(task1);
        tasks.add(task2);
        tasks.add(task3);

        for (MyTask task : tasks)
        {
            /*可以使用线程的CompletableFuture可以使用异步任务这个线程池CompletableFuture.supplyAsync
            使用纯异步的方式去调用第一个任务,第一个任务如果有了结果之后,
            用callback处理结果。

            */
            CompletableFuture f = CompletableFuture.supplyAsync(() -> task.runTask())
                    .thenAccept((result -> callback(result,task)));
        }
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private static Result callback(Result result,MyTask task)
    {
        //在处理结果的时候,判断结果如果是FAIL那么让所有的任务全部cancel
        if (Result.FAIL == result)
        {
            for (MyTask ts : tasks )
            {
                if (ts!=task)
                {
                    ts.cancel();
                }
            }
        }
        return result;
    }
    private static  class MyTask
    {
        private String name;
        private int timeInSeconds;
        private Result ret;

        boolean cancelling = false;
        volatile boolean cancelled = false;

        public MyTask(String name, int timeInSeconds, Result ret) {
            this.name = name;
            this.timeInSeconds = timeInSeconds * 1000 ;
            this.ret = ret;
        }

        public Result runTask()
        {
            int interval = 100;
            int total = 0;
            try {
                for (; ;)
                {
                    Thread.sleep(interval);
                    if (cancelling) continue;
                    total += interval;
                    if (total >= timeInSeconds)break;
                    if (cancelled) return Result.CANCELLED;
                }
            }catch (Exception e)
            {
                e.printStackTrace();
            }
            System.out.println(name+"end!");
            return ret;
        }

        //任务cancel过程synchronized (this)
        public void cancel()
        {
            cancelling = true;
            //不上锁的话关系也不大
            synchronized (this)
            {
                System.out.println(name+"cancelling");

                try {
                    //然后让它去任务结束
                    Thread.sleep(50);

                    System.out.println(name+"cancelled");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //最后cancel
                cancelled = true;
            }
        }
    }
}

总结

以上就是今天要讲的内容,以上代码还有可扩展性,当然“笔者还深度欠缺,如果错误还请指正”。

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
线程异步操作回滚事务可以通过使用`@Transactional`注解和`TransactionSynchronizationManager`类来实现。具体步骤如下: 1.在异步方法上添加`@Transactional(rollbackFor = Exception.class)`注解,表示该方法需要进行事务管理,并且在出现异常时需要回滚事务。 2.在同步方法中调用异步方法时,使用`TransactionSynchronizationManager.registerSynchronization()`方法注册一个事务同步器,该同步器会在事务提交或回滚时被调用。 3.在事务同步器中调用`TransactionSynchronizationManager.isActualTransactionActive()`方法判断当前是否有活动的事务,如果有,则调用`TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()`方法将事务标记为回滚状态。 下面是一个示例代码: ```java @Service public class MyService { @Autowired private MyAsyncService myAsyncService; @Transactional(rollbackFor = Exception.class) public void doSomething() { // 业务代码... try { // 数据库操作... } catch (Exception e) { // 调用异步方法完成数据库操作 myAsyncService.doSomethingAsync(); // 注册一个事务同步器 TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCompletion(int status) { // 判断当前是否有活动的事务 if (TransactionSynchronizationManager.isActualTransactionActive()) { // 将事务标记为回滚状态 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } }); } } } @Service public class MyAsyncService { @Async @Transactional(rollbackFor = Exception.class) public void doSomethingAsync() { // 异步方法业务代码... try { // 异步方法数据库操作... } catch (Exception e) { // 抛出异常,事务会自动回滚 throw new RuntimeException(e); } } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

id_lian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值