两道分析题的处理

简介: 今天下午一位前辈给我出了两道要写代码的分析题,说的晚上要交过去,现在已经很晚了。题目10个线程计算1000000个数的和自己手写Spring事务的处理逻辑,包含传播级别的处理解题目1在打完电话之后就直接写了,比我想象中要简单一些.思路:10个线程,那么每个线程负责数据中的一部分。

今天下午一位前辈给我出了两道要写代码的分析题,说的晚上要交过去,现在已经很晚了。

题目

  1. 10个线程计算1000000个数的和
  2. 自己手写Spring事务的处理逻辑,包含传播级别的处理

题目1

在打完电话之后就直接写了,比我想象中要简单一些.

思路:

  1. 10个线程,那么每个线程负责数据中的一部分。
  2. 所有的数据完成之后要能通知最后的求和线程,所有线程都完成的话需要一个临界条件
  3. 每个线程处理逻辑基本一样

求和运算操作继承自Runnable接口,在操作完之后将求和线程的个数加1,并且判断是否全部都运算完毕,如果全部都算完了,那么通知主线程计算。

假设运算不会溢出

public class NumberSum {
    /**
     * 要计算的数的数量,10000
     */
    private static int CONST = 1000000;

    /**
    * 总共的线程数量
    */
    private static int THREAD_COUTN = 10;
    
    /**
    * 每个线程负责计算的数据块大小
    */
    private static int SEGMENT_COUNT = CONST / THREAD_COUTN;
    
    /**
    * 要求和的所有数据存放位置
    */
    private static Long[] nums = new Long[CONST];
    
    /**
    * 所有线程求出的总和存放位置
    */
    private static Long[] result = new Long[THREAD_COUTN];
    
    /**
    * 完成求和的线程的个数
    */
    private static AtomicInteger complete = new AtomicInteger(0);

    private static final Object lock = new Object();

    public static void main(String[] args) {
        long startTime = System.nanoTime();

        for (int i = 0; i < CONST; i++) {
            nums[i] = Long.valueOf(i);
        }

        Compute compute = new Compute();
        for (int i = 0; i < THREAD_COUTN; i++) {
            new Thread(compute, String.valueOf(i)).start();
        }

        Long sum = 0L;
        synchronized (lock) {
            try {
                System.out.println("等待计算结果");
                // 可能其他线程已经计算完结果了,就无需等待 
                if (complete.get() < 10){
                    lock.wait();
                }
                System.out.println("计算完成");
                for (int i = 0; i < THREAD_COUTN; i++) {
                    sum = sum + result[i];
                }

                System.out.println("最终求得的结果为:" + sum);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        long endTime = System.nanoTime();
        double second = (endTime - startTime) / Math.pow(10,9);
        System.out.println("总共运行时间:"  + second  + "秒");

    }

    /**
     * Runable可以为多个线程共享
     */
    static class Compute implements Runnable {

        @Override
        public void run() {
            int segment = Integer.parseInt(Thread.currentThread().getName());
            int beginIndex = segment * SEGMENT_COUNT;
            int endIndex = (segment + 1) * SEGMENT_COUNT;
            Long sum = 0L;
            for (int i = beginIndex; i < endIndex; i++) {
                sum = sum + nums[i];
            }
            result[segment] = sum;
            System.out.println("线程" + segment + "求得的结果为:" + sum);
            synchronized (lock){
                int completeNumber = complete.addAndGet(1);
                if (completeNumber == THREAD_COUTN){
                    lock.notifyAll();
                }
            }

        }
    }
}

题目2

Spring事务处理代码阅读

这个题目,我想了下,事务中包含了不少要了解的东西,我在写事务的时候,走偏了一点,低估了手写事务的难度,对事务的理解程度不够深刻,没有能很好的剥离出事务处理的核心部分。

几个核心类:

  • TransactionInterceptor AOP的Advice,方法拦截器,配置的时候用的是它
  • TransactionAspectSupport,事务拦截器的父类,提供了一些模板方法,早先的Spring版本,方法在TransactionInterceptor内,新版的Spring把一些方法放到这个对象中了。
  • PlatformTransactionManager ,真正处理事务生命周期的东西,begin,commit,rollback都在PlatformTransactionManager内实现
  • TransactionAttributeSource,这个对指定具体事务执行的属性进行了封装。具体事务配置的属性都在里面
Spring事务处理基本结构
img_14aabf0e3f0d0ffe1baee2161f2dda3e.png
事务基本结构

else部分的逻辑暂时用不到

  1. 获取定义的attrbute,事务的配置部分,配置中是可以指定具体的事务管理器的。
  2. 根据事务的属性,判断是不是要创建事务,事务的传播级别处理都在里面,对应第277行
  3. 执行事务体内的方法。invocation.proceedWithInvocation()。我们的事务代码部分,一般就是我们队数据库的操作代码
  4. 如果有异常,处理异常的部分, 286行
  5. 不管有无异常执行完之后,清除事务信息。
  6. 如果正确执行的话,就commit结果,并且返回我们程序返回的值
Spring判断是否要创建事务
img_958986c4525f3ddf50293d29e5e09b48.png
截图自TransactionAspectSupport.java
  1. 重点部分在461行,事务属性与事务管理器都不为空的话,那么事务管理器根据指定的事务属性获取事务。
  2. 最后的prepareTransaction知识把事务信息绑定到当前线程。

事务管理器内部都是有关事务的操作,这个知识抽象的事务管理器,作为模板方法,真正的事务处理需要其继承类来实现。


img_74f58cd5b870dd4fd19f27abfe977526.png
image.png
事务传播级别的处理

抽象事务管理器内部,getTransaction主要用来处理 事务的传播级别的逻辑,代理一些方法到doGetTransaction,isExistingTransaction,doBegin方法上

public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
        Object transaction = doGetTransaction();

        //缓存日志级别,避免重复检查
        boolean debugEnabled = logger.isDebugEnabled();

        if (definition == null) {
            // Use defaults if no transaction definition given.
            definition = new DefaultTransactionDefinition();
        }

        if (isExistingTransaction(transaction)) {
            // Existing transaction found -> check propagation behavior to find out how to behave.
            return handleExistingTransaction(definition, transaction, debugEnabled);
        }

        // Check definition settings for new transaction.
        if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
            throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
        }

        // No existing transaction found -> check propagation behavior to find out how to proceed.
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException(
                    "No existing transaction found for transaction marked with propagation 'mandatory'");
        }
        else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            SuspendedResourcesHolder suspendedResources = suspend(null);
            if (debugEnabled) {
                logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
            }
            try {
                boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
                DefaultTransactionStatus status = newTransactionStatus(
                        definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                doBegin(transaction, definition);
                prepareSynchronization(status, definition);
                return status;
            }
            catch (RuntimeException ex) {
                resume(null, suspendedResources);
                throw ex;
            }
            catch (Error err) {
                resume(null, suspendedResources);
                throw err;
            }
        }
        else {
            // Create "empty" transaction: no actual transaction, but potentially synchronization.
            if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
                logger.warn("Custom isolation level specified but no actual transaction initiated; " +
                        "isolation level will effectively be ignored: " + definition);
            }
            boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
            return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
        }
    }

img_1dc277e67b52a861b0dcb9ba7f9f8bd0.png
传播级别处理的部分
  1. 如果存在了事务,那么处理已经存在的事务,在已经存在的事务内进行事务传播级别的处理。是否存在事务的代码交给其继承类来实现,自己默认返回false
  2. 没有事务的话,就直接根据事务传播级别的定义进行处理
img_6c980c519c10aa146932877abd33301c.png
常见传播级别

使用NESTED传播级别的时候,底层数据源必须基于JDBC3.0,并且实现者需要支持保存点事务机制。


img_88016eeffc4041b3e22ad0a3f6eba0f5.png
NESTED事务传播级别
仿写的部分

代码内容:
基于AOP注解的方法拦截器,在进行方法拦截的时候,我们可以获取到方法上的注解值,通过注解的值,可以了解到事务的传播级别。

一个事务如何判断是否在其它事务内呢?
我用的是一个事务栈,使用LinkedList作为栈来处理,LinkedList是线程私有的,这样实现不同的线程也不会相互干扰,如果栈内已经存在事务标志了,那么就代表当前操作是一件在事务内的。

代码相对粗糙。

依赖:


    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.16.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.16.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.3.16.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
    </dependencies>

事务拦截器:

/**
 * @author aihe 2018/7/16
 */

public class TransactionInterceptor implements MethodInterceptor {

    /**
     * 事务栈,为每个线程设置独立的事务栈,如果栈内存在标志,代表当前操作依据在事务内
     */
    private ThreadLocal<LinkedList<TransactionFlag>> tx = new ThreadLocal<LinkedList<TransactionFlag>>();

    /**
     * 事务方法上指定的回滚事务异常类型。
     */
    private ThreadLocal<LinkedList<Class<? extends Throwable>[]>> excetionStack = new ThreadLocal<LinkedList<Class<? extends Throwable>[]>>();

    /**
     * 1. 创建事务,根据当前的情况,判断是否要创建
     * 2. 执行事务内的实际的具体逻辑
     * 3. 如果发生异常,判断是否要回滚
     * @param methodInvocation
     * @return
     * @throws Throwable
     */
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {

        createTransactionIfNecessary(methodInvocation);
        Object result = null;
        try {
            result = methodInvocation.proceed();
        } catch (Exception e) {
            completeTransactionAfterThrowing(e);
            throw e;
        }

        return result;
    }

    /**
     * 处理事务回滚逻辑,如果不存在事务直接跳过,否则根据异常类型判断是否要进行事务回滚。
     * @param e
     * @throws Exception
     */
    private void completeTransactionAfterThrowing(Exception e) throws Exception {
        if (tx.get() != null){
            Class<? extends Throwable>[] first = excetionStack.get().getFirst();
            for (Class<? extends Throwable> aClass : first) {
                if (e.getClass() == aClass) {
                    System.out.println("回滚事务");
                    throw e;
                }
            }
        }
    }

    private void createTransactionIfNecessary(MethodInvocation methodInvocation) {
        LinkedList<TransactionFlag> transactionFlags = tx.get();
        LinkedList<Class<? extends Throwable>[]> throwables = excetionStack.get();
        Method method = methodInvocation.getMethod();
        Transactional annotation = method.getAnnotation(Transactional.class);

        //获取传播属性与异常
        Propagation propagation = annotation.propagation();
        Class<? extends Throwable>[] rollbackFor = annotation.rollbackFor();
        throwables.add(rollbackFor);

        if (propagation == Propagation.MANDATORY) {
            throw new IllegalTransactionStateException(
                    "没有已经存在的事务");
        }

        if (propagation == Propagation.REQUIRES_NEW ||
                propagation == Propagation.REQUIRED ||
                propagation == Propagation.NESTED
                ) {
            //不存在事务
            if (transactionFlags == null ){
                transactionFlags = new LinkedList<TransactionFlag>();
                transactionFlags.add(new TransactionFlag());
                tx.set(transactionFlags);
                System.out.println("新建事务");
            //已经存在事务
            }else{
                if (propagation == Propagation.REQUIRES_NEW ){
                    transactionFlags.add(new TransactionFlag());
                    tx.set(transactionFlags);
                    System.out.println("挂起当前事务");
                }
            }
        }


    }

    /**
     * 事务标志
     */
    private static class TransactionFlag {

    }
}

写不出那么厉害的代码... 有些像伪代码

最后

事务处理的代码这块自己写的确实不够好。

昨天挂完电话,前辈的意思是挂完电话,立刻写代码然后发给他吗,如果是这样,上面的算法计数题可以写的出来,
我事务的部分写的也是不会表现太好。

在思考事务如何写的时候,事务如果往大了写,那就是自己造一个精简版的事务轮子,我前两个小时是这么想的,但分析不到位,没有那么简单,也写不出来。
往小了写,事务的核心逻辑剥离出来,整体思路要明白。对于我,事务的原理要仔细研究啊。事务的代码表现的不够好。

相关文章
|
7月前
acwing 恨7不成妻
acwing 恨7不成妻
56 0
|
7月前
|
机器学习/深度学习
蓝桥杯-2/14天-完全平方数【另类思路】
蓝桥杯-2/14天-完全平方数【另类思路】
【洛谷算法题】B2029-大象喝水【入门1顺序结构】
【洛谷算法题】B2029-大象喝水【入门1顺序结构】
测量学的几道简答题
测量学的几道简答题
70 0
|
存储
剑指Offer - 面试题14:剪绳子
剑指Offer - 面试题14:剪绳子
87 0
|
并行计算 C++
这道小学六年级的数学题,恕我直言没几个人会做
这道小学六年级的数学题,恕我直言没几个人会做
470 0
|
算法
【AcWing&&牛客】打表找规律
【AcWing&&牛客】打表找规律
92 0
|
算法 JavaScript 前端开发
日拱算法:解两道“杨辉三角”题
什么是“杨辉三角”,想必大家并不陌生~~ 在「杨辉三角」中,每个数是它左上方和右上方的数的和。
|
前端开发 程序员 C语言
LeetCode | 循环队列的爱情【恋爱法则——环游世界】
环形队列包含真挚的我们 ❤ 兜兜转换最后还是你
112 0
LeetCode | 循环队列的爱情【恋爱法则——环游世界】