天天看點

兩道分析題的處理

今天下午一位前輩給我出了兩道要寫代碼的分析題,說的晚上要交過去,現在已經很晚了。

題目

  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事務處理基本結構

兩道分析題的處理

事務基本結構

else部分的邏輯暫時用不到

  1. 擷取定義的attrbute,事務的配置部分,配置中是可以指定具體的事務管理器的。
  2. 根據事務的屬性,判斷是不是要建立事務,事務的傳播級别處理都在裡面,對應第277行
  3. 執行事務體内的方法。invocation.proceedWithInvocation()。我們的事務代碼部分,一般就是我們隊資料庫的操作代碼
  4. 如果有異常,處理異常的部分, 286行
  5. 不管有無異常執行完之後,清除事務資訊。
  6. 如果正确執行的話,就commit結果,并且傳回我們程式傳回的值

Spring判斷是否要建立事務

兩道分析題的處理

截圖自TransactionAspectSupport.java

  1. 重點部分在461行,事務屬性與事務管理器都不為空的話,那麼事務管理器根據指定的事務屬性擷取事務。
  2. 最後的prepareTransaction知識把事務資訊綁定到目前線程。

事務管理器内部都是有關事務的操作,這個知識抽象的事務管理器,作為模闆方法,真正的事務處理需要其繼承類來實作。

兩道分析題的處理

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);
        }
    }

           
兩道分析題的處理

傳播級别處理的部分

  1. 如果存在了事務,那麼處理已經存在的事務,在已經存在的事務内進行事務傳播級别的處理。是否存在事務的代碼交給其繼承類來實作,自己預設傳回false
  2. 沒有事務的話,就直接根據事務傳播級别的定義進行處理
兩道分析題的處理

常見傳播級别

使用NESTED傳播級别的時候,底層資料源必須基于JDBC3.0,并且實作者需要支援儲存點事務機制。

兩道分析題的處理

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 {

    }
}

           

寫不出那麼厲害的代碼... 有些像僞代碼

最後

事務處理的代碼這塊自己寫的确實不夠好。

昨天挂完電話,前輩的意思是挂完電話,立刻寫代碼然後發給他嗎,如果是這樣,上面的算法計數題可以寫的出來,

我事務的部分寫的也是不會表現太好。

在思考事務如何寫的時候,事務如果往大了寫,那就是自己造一個精簡版的事務輪子,我前兩個小時是這麼想的,但分析不到位,沒有那麼簡單,也寫不出來。

往小了寫,事務的核心邏輯剝離出來,整體思路要明白。對于我,事務的原理要仔細研究啊。事務的代碼表現的不夠好。

上一篇: 畢業之後