面試要點4

---java的類加載機制的原理?---

類加載是一個將類合併到正在運行着的JVM進程中的過程。首先要加載一個類,咱們必須先得將類文件加載進來並鏈接,而且要加上大量的驗證,隨後會生成一個表明着這個類的class對象,而後就能夠經過它去建立新的實例了。
    這就是我所理解的Java的類加載機制。
    通過加載和鏈接後出來的class對象,說明這個類已經被加載到了JVM中,此後就不會再加載了。

 ---分佈式session共享怎麼設計?---

1、Session Replication 方式管理 (即session複製)
簡介:將一臺機器上的Session數據廣播複製到集羣中其他機器上
使用場景:機器較少,網絡流量較小
優勢:實現簡單、配置較少、當網絡中有機器Down掉時不影響用戶訪問
缺點:廣播式複製到其他機器有必定廷時,帶來必定網絡開銷
2、Session Sticky 方式管理
簡介:即粘性Session、當用戶訪問集羣中某臺機器後,強制指定後續全部請求均落到此機器上
使用場景:機器數適中、對穩定性要求不是很是苛刻
優勢:實現簡單、配置方便、沒有額外網絡開銷
缺點:網絡中有機器Down掉時、用戶Session會丟失、容易形成單點故障
3、緩存集中式管理
簡介:將Session存入分佈式緩存集羣中的某臺機器上,當用戶訪問不一樣節點時先從緩存中拿Session信息
使用場景:集羣中機器數多、網絡環境複雜
優勢:可靠性好
缺點:實現複雜、穩定性依賴於緩存的穩定性、Session信息放入緩存時要有合理的策略寫入前端

 ---ThreadLocal---

ThreadLocal,顧名思義,它不是一個線程,而是線程的一個本地化對象。當工做於多線程中的對象使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程分配一個獨立的變量副本。因此每個線程均可以獨立地改變本身的副本,而不會影響其餘線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量,這也是類名中「Local」所要表達的意思。java

一、ThreadLocal不是線程,是線程的一個變量,你能夠先簡單理解爲線程類的屬性變量。mysql

  二、ThreadLocal在類中一般定義爲靜態變量。web

  三、每一個線程有本身的一個ThreadLocal,它是變量的一個「拷貝」,修改它不影響其餘線程。spring

  既然定義爲類變量,爲什麼爲每一個線程維護一個副本(姑且稱爲「拷貝」容易理解),讓每一個線程獨立訪問?多線程編程的經驗告訴咱們,對於線程共享資源(你能夠理解爲屬性),資源是否被全部線程共享,也就是說這個資源被一個線程修改是否影響另外一個線程的運行,若是影響咱們須要使用synchronized同步,讓線程順序訪問。sql

  ThreadLocal適用於資源共享但不須要維護狀態的狀況,也就是一個線程對資源的修改,不影響另外一個線程的運行;這種設計是‘空間換時間’,synchronized順序執行是‘時間換取空間’。數據庫

---Mysql的兩種存儲引擎以及區別---

 

1、Mysql的兩種存儲引擎編程

  一、MyISAM:bootstrap

    ①不支持事務,可是整個操做是原子性的(事務具有四種特性:原子性、一致性、隔離性、持久性)數組

    ②不支持外鍵,支持表鎖,每次所住的是整張表

        MyISAM的表鎖有讀鎖和寫鎖(兩個鎖都是表級別):

      表共享讀鎖和表獨佔寫鎖。在對MyISAM表進行讀操做時,不會阻塞其餘用戶對同一張表的讀請求,可是會阻塞其餘用戶對錶的寫請求;對其進行寫操做時會阻塞對同一表讀操做和寫操做

      MyISAM存儲引擎的讀鎖和寫鎖是互斥的,讀寫操做是串行的。那麼,一個進程請求某個MyISAM表的讀鎖,同時另外一個進程也請求同一表的寫鎖,MySQL如何處理呢?答案是寫進程先得到鎖。不只如此,即便讀請求先到鎖等待隊列,寫請求後到,寫鎖也會插到讀鎖請求以前!這是由於MySQL認爲寫請求通常比讀請求要重要。這也正是MyISAM表不太適合於有大量更新操做和查詢操做應用的緣由,由於,大量的更新操做會形成查詢操做很難得到讀鎖,從而可能永遠阻塞。這種狀況有時可能會變得很是糟糕! 

    ③一個MyISAM表有三個文件:索引文件,表結構文件,數據文件

    ④存儲表的總行數,執行select count(*) from table時只要簡單的讀出保存好的行數便可

      (myisam存儲引擎的表,count(*)速度快的也僅僅是不帶where條件的count。這個想一想容易理解的,由於你帶了where限制條件,原來因此中緩存的表總數可以直接返回用嗎?不能用。這個查詢引擎也是須要根據where條件去表中掃描數據,進行統計返回的。)

    ⑤採用非彙集索引,索引文件的數據域存儲指向數據文件的指針。輔索引與主索引基本一致,可是輔索引不用保證惟一性。

    ⑥支持全文索引和空間索引

    ⑦對於AUTO_INCREMENT類型的字段,在MyISAM表中,能夠和其餘字段一塊兒創建聯合索引。

        二、Innodb:

    ①支持事務,支持事務的四種隔離級別;是一種具備事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。

    ②支持行鎖和外鍵約束,所以能夠支持寫併發

    ③不存儲總行數;也就是說,執行select count(*) from table時,InnoDB要掃描一遍整個表來計算有多少行。注意的是,當count(*)語句包含 where條件時,兩種表的操做是同樣的。

    ④對於AUTO_INCREMENT類型的字段,InnoDB中必須包含只有該字段的索引

    ⑤DELETE FROM table時,InnoDB不會從新創建表,而是一行一行的刪除

    ⑥一個Innodb表存儲在一個文件內(共享表空間,表大小不受操做系統的限制),也可能爲多個(設置爲獨立表空間,表大小受操做系統限制,大小爲2G),受操做系統文件大小的限制

    ⑦主鍵索引採用彙集索引(索引的數據域存儲數據文件自己),輔索引的數據域存儲主鍵的值;所以從輔索引查找數據,須要先經過輔索引找到主鍵值,再訪問主鍵索引;最好使用自增主鍵,防止插入數據時,爲維持B+樹結構,文件的大調整。

---Spring Boot 的核心配置文件有哪幾個?它們的區別是什麼?---

Spring Boot 的核心配置文件是 application 和 bootstrap 配置文件。

application 配置文件這個容易理解,主要用於 Spring Boot 項目的自動化配置。

bootstrap 配置文件有如下幾個應用場景。

  • 使用 Spring Cloud Config 配置中心時,這時須要在 bootstrap 配置文件中添加鏈接到配置中心的配置屬性來加載外部配置中心的配置信息;
  • 一些固定的不能被覆蓋的屬性;
  • 一些加密/解密的場景;

---Spring Boot 的配置文件有哪幾種格式?它們有什麼區別?---

.properties 和 .yml,它們的區別主要是書寫格式不一樣。

1).properties : 例 app.user.name = javastack

2).yml : 例 app:

        user:

                            name: javastack

另外,.yml 格式不支持  @PropertySource 註解導入配置。

---Spring Boot 的核心註解是哪一個?它主要由哪幾個註解組成的?---

啓動類上面的註解是@SpringBootApplication,它也是 Spring Boot 的核心註解,主要組合包含了如下 3 個註解:

@SpringBootConfiguration:組合了 @Configuration 註解,實現配置文件的功能。

@EnableAutoConfiguration:打開自動配置的功能,也能夠關閉某個自動配置的選項,如關閉數據源自動配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。

@ComponentScan:Spring組件掃描。

---運行 Spring Boot 有哪幾種方式?---

1)打包用命令或者放到容器中運行

2)用 Maven/ Gradle 插件運行

3)直接執行 main 方法運行

---Spring Boot 能夠兼容老 Spring 項目嗎,如何作?---

能夠兼容,使用 @ImportResource 註解導入老 Spring 項目配置文件。

---你如何理解 Spring Boot 中的 Starters?---

Starters能夠理解爲啓動器,它包含了一系列能夠集成到應用裏面的依賴包,你能夠一站式集成 Spring 及其餘技術,而不須要處處找示例代碼和依賴包。如你想使用 Spring JPA 訪問數據庫,只要加入 spring-boot-starter-data-jpa 啓動器依賴就能使用了。

Starters包含了許多項目中須要用到的依賴,它們能快速持續的運行,都是一系列獲得支持的管理傳遞性依賴。

 ---spring註解事務Transactional自調用失效---

 若是一個類中自身方法的調用,咱們稱之爲自調用。如一個訂單業務實現類OrderServiceImpl中有methodA方法調用了自身類的methodB方法就是自調用,如:

若是一個類中自身方法的調用,咱們稱之爲自調用。如一個訂單業務實現類OrderServiceImpl中有methodA方法調用了自身類的methodB方法就是自調用,如:

@Transactional public void methodA(){ for (int i = 0; i < 10; i++) { methodB(); } } @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW) public int methodB(){ ...... }

在上面方法中無論methodB如何設置隔離級別和傳播行爲都是不生效的。即自調用失效。

這主要是因爲@Transactional的底層實現原理是基於AOP實現,而AOP的原理是動態代理,在自調用的過程當中是類自身的調用,而不是代理對象去調用,那麼就不會產生AOP,因而就發生了自調用失敗的現象。

要克服這個問題,有2種方法:

  • 編寫兩個Service,用一個Service的methodA去調用另一個Service的methodB方法,這樣就是代理對象的調用,不會有問題;
  • 在同一個Service中,methodA不直接調用methodB,而是先從Spring IOC容器中從新獲取代理對象`OrderServiceImpl·,獲取到後再去調用methodB。提及來有點亂,仍是show you the code。
public class OrderServiceImpl implements OrderService,ApplicationContextAware { private ApplicationContext applicationContext = null; @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } @Transactional public void methodA(){ OrderService orderService = applicationContext.getBean(OrderService.class); for (int i = 0; i < 10; i++) { orderService.methodB(); } } @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW) public int methodB(){ ...... } }

上面代碼中咱們先實現了ApplicationContextAware接口,而後經過applicationContext.getBean()獲取了OrderService的接口對象。這個時候獲取到的是一個代理對象,也就能正常使用AOP的動態代理了。

 ---spring對於有狀態bean的併發問題解決方案---

兩種解決方案:

1.將有狀態的bean配置成prototype模式,讓每個線程都建立一個prototype實例。可是這樣會產生不少的實例消耗較多的內存空間。

2.使用ThreadLocal變量,爲每一條線程設置變量副本。(典型應用就是從數據庫鏈接池中獲取connection對象,同一個線程共享,不一樣線程隔離)

 

 ---mysql的時間字段問題---

MySQL中用來表示時間的字段類型有:DATE、DATETIME、TIMESTAMP,它們之間有相同點,各自也有本身的特性,我總結了一個表格,以下所示:image.png

在開發中,應該儘可能避免使用時間戳做爲查詢條件(如:selelct * from user where modified_time >= #{date}),若是必需要用,則須要充分考慮MySQL的精度和查詢參數的精度等問題。例如:mysql-connector-java在5.1.23以前會將秒後面的精度丟棄再傳給MySQL服務端,正好咱們使用的mysql版本中DATETIME的精度是秒;在我將mysql-connector-java升級到5.1.30後,從java應用經過mysql-connector-java將時間戳傳到MySQL服務端的時候,就不會將毫秒數丟棄了,從mysql-connector-java的角度看是修復了一個BUG,可是對於咱們的應用來講卻可能觸發了一個BUG。

 --- javaweb filter---

Filter是一個實現了javax.servlet.Filter接口的類。

Filter接口中的方法:

  • init(FilterConfig  filterFonfig)    //初始化Filter
  • doFilter(ServletRequest request, ServletResponse response, FilterChain  chain)     //攔截、過濾。chain對象表示Filter鏈。此方法是Filter的關鍵方法。
  • destroy()   //在web服務器移除Filter對象以前調用,釋放Filter對象佔用的資源
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        //......    //去的時候攔截,作一些處理
        chain.doFilter(req, resp);  //放行
        //......    //回來的時候攔截,作一些處理
    }

 Filter接口兩種配置方式:

(1)web.xml中配置

<filter>
        <filter-name>handlerFilter</filter-name>
        <filter-class>filter.HandlerFilter</filter-class>
        <init-param>
            <param-name>name</param-name>
            <param-value>張三</param-value>
        </init-param>
        <init-param>
            <param-name>age</param-name>
            <param-value>20</param-value>
        </init-param>
    </filter>

若是有多個Filter同時攔截某個請求,這些Filter會組成一個FilterChain,攔截時,在xml配置中位置靠前的Filter-mapping先攔截。

(2)註解配置

@WebFilter(
        filterName = "handlerFilter",
        urlPatterns = "/*",
        initParams = {@WebInitParam(name = "name", value = "張三"),@WebInitParam(name="age",value = "12")}
        )

Filter接口使用示例:

public class HandlerFilter implements Filter {
private FilterConfig filterConfig; //須要建立一個成員變量
public void init(FilterConfig config){
this.filterConfig = config; //須要咱們手動初始化
}

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
//獲取單個初始參數的值
String name = filterConfig.getInitParameter("name"); //返回值是String,不存在該參數時返回null
String age = filterConfig.getInitParameter("age");
System.out.println(name);
System.out.println(age);

//遍歷
Enumeration<String> initParameterNames = filterConfig.getInitParameterNames();
while (initParameterNames.hasMoreElements()){
String paramName = initParameterNames.nextElement();
String paramValue = filterConfig.getInitParameter(paramName);
System.out.println(paramValue);
}

chain.doFilter(req, resp);
}

public void destroy() {
}

}

 ---Executor線程池---

引用自《阿里巴巴JAVA開發手冊》

【強制】線程資源必須經過線程池提供,不容許在應用中自行顯式建立線程。

說明:使用線程池的好處是減小在建立和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。若是不使用線程池,有可能形成系統建立大量同類線程而致使消耗完內存或者「過分切換」的問題。

 

Executor接口是線程池框架中最基礎的部分,定義了一個用於執行Runnable的execute方法。Exectuor下有一個重要的子接口ExecutorService,其中定義了線程池的具體行爲

    • execute(Runnable runnable):執行Runnable類型的任務
    • submit(task):用來提交Callable或者Runnable任務,並返回表明此任務的Future對象
    • shutdown():在完成已經提交的任務後封閉辦事,不在接管新的任務
    • shutdownNow():中止全部正在履行的任務並封閉辦事
    • isTerminated():是一個鉤子函數,測試是否全部任務都履行完畢了
    • isShutdown():是一個鉤子函數,測試是否該ExecutorService是否被關閉

線程池的狀態

線程池使用了一個Integer類型變量來記錄線程池任務數量和線程池狀態信息,很巧妙。這個變量ctl,被定義爲了AtomicInteger,使用高3位來表示線程池狀態低29位來表示線程池中的任務數量

RUNNING = ­1 << COUNT_BITS; //高3位爲111
SHUTDOWN = 0 << COUNT_BITS; //高3位爲000
STOP = 1 << COUNT_BITS; //高3位爲001
TIDYING = 2 << COUNT_BITS; //高3位爲010
TERMINATED = 3 << COUNT_BITS; //高3位爲011

根據代碼設計,咱們用圖標來展現一下

 

 

 

一、RUNNING

  • 狀態說明:線程池處於RUNNING狀態,可以接收新任務,以及對已添加的任務進行處理。
  • 狀態切換:線程池的初始化狀態是RUNNING。換句話說,線程池一旦被建立,就處於RUNNING狀態,而且線程池中的任務數爲0。

二、SHUTDOWN

  • 狀態說明:線程池處於SHUTDOWN狀態,不接收新任務,可以處理已經添加的任務。
  • 狀態切換:調用shutdown()方法時,線程池由RUNNING -> SHUTDOWN。

三、STOP

  • 狀態說明:線程池處於STOP狀態,不接收新任務,不處理已提交的任務,而且會中斷正在處理的任務。
  • 狀態切換:調用線程池中的shutdownNow()方法時,線程池由(RUNNING or SHUTDOWN) -> STOP。

四、TIDYING

  • 狀態說明:當全部的任務已經中止,ctl記錄「任務數量」爲0,線程池會變爲TIDYING狀態。當線程池處於TIDYING狀態時,會執行鉤子函數 terminated()。 terminated()在ThreadPoolExecutor類中是空, 的,若用戶想在線程池變爲TIDYING時,進行相應處理,能夠經過重載 terminated()函數來實現。
  • 狀態切換:當線程池在SHUTDOWN狀態下,阻塞隊列爲空而且線程池中執行任務也爲空時,就會由SHUTDOWN -> TIDYING。當線程池在STOP狀態下,線程池中執行的任務爲空時,就會由STOP-> TIDYING。

五、TERMINATED

  • 狀態說明:線程池線程池完全中止,線程池處於TERMINATED狀態,
  • 狀態切換:線程池處於TIDYING狀態時,執行完terminated()以後, 就會由TIDYING->TERMINATED。
線程池的建立
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
{ this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }
  • corePoolSize:線程池中的核心線程數。當提交一個任務時,線程池建立一個新線程執行任務,直到當前線程數等於corePoolSize;若是當前線程數爲corePoolSize,繼續提交的任務被保存到阻塞隊列中,等待被執行;若是執行了線程池的prestartAllCoreThreads()方法,線程池會提早建立並啓動全部核心線程。除非設置了allowCoreThreadTimeOut,不然核心線程將持續保留在線程池中即時沒有新的任務提交過來。
  • maximumPoolSize:線程池中容許的最大線程數。若是當前阻塞隊列滿了,且繼續提交任務,則建立新的線程執行任務,前提是當前線程數小於maximumPoolSize。
  • keepAliveTime:線程池維護線程所容許的空閒時間。當線程池中的線程數量大於corePoolSize時候,若是這時候沒有新的任務提交,核心線程外的線程不會當即被銷燬,而是會等待,直到等待的時間超過了keepAliveTime
    unit:keepAliveTime的單位時間
  • workQueue:用於保存等待被執行的任務的阻塞隊列,且任務必須實現Runnable接口,在JDK中提供了以下阻塞隊列:
    ArrayBlockingQueue:基於數組結構的有界阻塞隊列,按FIFO排序任務。
    LinkedBlockingQueue:基於鏈表結構的阻塞隊列,按FIFO排序任務,吞吐量一般要高於ArrayBlockingQueue。
    SynchronousQueue:一個不存儲元素的阻塞隊列,每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般高於LinkedBlockingQueue。
  • PriorityBlockingQueue:具備優先級的無界阻塞隊列。
  • threadFactory:ThreadFactory 類型的變量,用來建立新線程。默認使用ThreadFactory.defaultThreadFactory來建立線程, 會使新建立線程具備相同的NORM_PRIORITY優先級而且都是非守護線程,同時也設置了線程名稱。
  • handler:線程池的飽和策略。當阻塞隊列滿了,且沒有空閒的工做隊列,若是繼續提交任務,必須採用一種策略處理該任務.

咱們簡化下上面的文字,用簡單表格來展現:

咱們再用流程圖來對線程池中提交任務的這一邏輯增長感性認識:

線程池的監控

public long getTaskCount() //線程池已執行與未執行的任務總數
public long getCompletedTaskCount() //已完成的任務數
public int getPoolSize() //線程池當前的線程數
public int getActiveCount() //線程池中正在執行任務的線程數量

JDK提供的經常使用的線程池

通常狀況下咱們都不直接用ThreadPoolExecutor類來建立線程池,而是經過Executors工具類去構建,經過Executors工具類咱們能夠構造5種不一樣的線程池。

newFixedThreadPool(int nThreads):

建立固定線程數的線程池,corePoolSize和maximumPoolSize是相等的,默認狀況下,線程池中的空閒線程不會被回收的;

newCachedThreadPool:

建立線程數量不定的線程池,線程數量隨任務量變更,一旦來了新的任務,若是線程池中沒有空閒線程則立馬建立新的線程來執行任務,空閒線程存活時間60秒,事後就被回收了,可見這個線程池彈性很高;

newSingleThreadExecutor:

建立線程數量爲1的線程池,等價於newFixedThreadPool(1)所構造的線程池;

newScheduledThreadPool(int corePoolSize):

建立核心線程數爲corePoolSize,可執行定時任務的線程池;

newSingleThreadScheduledExecutor:

等價於newScheduledThreadPool(1)。

阻塞隊列

構造函數中的隊列容許咱們自定義,隊列的意義在於緩存沒法獲得線程執行的任務,當線程數量大於corePoolSize而當前workQueue尚未滿時,就須要將任務放置到隊列中。JDK提供了幾種類型的隊列容器,每種類型都具各自特色,能夠根據實際場景和須要自行配置到線程池中。

ArrayBlockingQueue:

有界隊列,基於數組結構,按照隊列FIFO原則對元素排序;

LinkedBlockingQueue:

無界隊列,基於鏈表結構,按照隊列FIFO原則對元素排序,Executors.newFixedThreadPool()使用了這個隊列;

SynchronousQueue:

同步隊列,該隊列不存儲元素,每一個插入操做必須等待另外一個線程調用移除操做,不然插入操做會一直被阻塞,Executors.newCachedThreadPool()使用了這個隊列;

PriorityBlockingQueue:

優先級隊列,具備優先級的無限阻塞隊列。

拒絕策略

拒絕策略(RejectedExecutionHandler)也稱飽和策略,當線程數量和隊列都達到飽和時,就採用飽和策略來處理新提交過來的任務,默認狀況下采用的策略是拋出異常(AbortPolicy),表示沒法處理直接拋出異常,其實JDK提供了四種策略,也很好記,拒絕策略無非就是拋異常、執行或者丟棄任務,其中丟棄任務就分爲丟棄本身或者丟棄隊列中最老的任務,下面簡要說明一下:

AbortPolicy:丟棄新任務,並拋出 RejectedExecutionException

DiscardPolicy:不作任何操做,直接丟棄新任務

DiscardOldestPolicy:丟棄隊列隊首(最老)的元素,並執行新任務

CallerRunsPolicy:由當前調用線程來執行新任務

使用技巧

使用了線程池技術未必可以給工做帶來利好,在沒能正確理解線程池特性以及瞭解自身業務場景下而配置的線程池,可能會成爲系統性能或者業務的瓶頸甚至是漏洞,因此在咱們使用線程池時,除了對線程池自己特性瞭如指掌,還須要對自身業務屬性進行一番分析,以便配置出合理的高效的線程池以供項目使用,下面咱們從這幾個方面來分析:

  • 任務的性質:CPU密集型任務,IO密集型任務和混合型任務。
  • 任務的優先級:高,中和低。
  • 任務的執行時間:長,中和短。
  • 任務的依賴性:是否依賴其餘系統資源,如數據庫鏈接。

性質不一樣的任務能夠用不一樣規模的線程池分開處理。CPU密集型任務配置儘量少的線程數量,如配置Ncpu+1個線程的線程池,以減小線程切換帶來的性能開銷。IO密集型任務則因爲須要等待IO操做,線程並非一直在執行任務,則配置儘量多的線程,如2*Ncpu。混合型的任務,若是能夠拆分,則將其拆分紅一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐率要高於串行執行的吞吐率,若是這兩個任務執行時間相差太大,則不必進行分解。咱們能夠經過Runtime.getRuntime().availableProcessors()方法得到當前設備的CPU個數。

優先級不一樣的任務可使用優先級隊列PriorityBlockingQueue來處理。它可讓優先級高的任務先獲得執行,須要注意的是若是一直有優先級高的任務提交到隊列裏,那麼優先級低的任務可能永遠不能執行。

固然,以上這些配置方式都是經驗值,實際當中還須要分析本身的項目場景通過屢次測試方可得出最適合本身項目的線程池配置。

 

 
 

 ---事務的ACID屬性之間的關係---

這幾個特性不是一種平級關係:

  • 只有知足一致性,事務的執行結果纔是正確的。
  • 在無併發的狀況下,事務串行執行,隔離性必定可以知足。此時要只要能知足原子性,就必定能知足一致性。
  • 在併發的狀況下,多個事務併發執行,事務不只要知足原子性,還須要知足隔離性,才能知足一致性。
  • 事務知足持久化是爲了能應對數據庫奔潰的狀況。

---事務的隔離級別---

MYSQL常看當前數據庫的事務隔離級別:show variables like 'tx_isolation';

 

---Springmvc的攔截器執行順序及各方法做用---

 

實現HandlerInterceptor接口或者繼承HandlerInterceptor的子類,好比Spring 已經提供的實現了HandlerInterceptor 接口的抽象類HandlerInterceptorAdapter ,下面講實現其接口的寫法,先看一下這個接口的三個方法. 


方法preHandle: 顧名思義,該方法將在請求處理以前進行調用,在controller以前執行。SpringMVC 中的Interceptor 是鏈式的調用的,在一個應用中或者說是在一個請求中能夠同時存在多個Interceptor 。每一個Interceptor 的調用會依據它的聲明順序依次執行,並且最早執行的都是Interceptor 中的preHandle 方法,因此能夠在這個方法中進行一些前置初始化操做或者是對當前請求的一個預處理,好比說獲取cookie的值或者判斷是否已經登陸,也能夠在這個方法中進行一些判斷來決定請求是否要繼續進行下去。該方法的返回值是布爾值Boolean 類型的,當它返回爲false 時,表示請求結束,後續的Interceptor 和Controller 都不會再執行;當返回值爲true 時就會繼續調用下一個Interceptor 的preHandle 方法,若是已是最後一個Interceptor 的時候就會是調用當前請求的Controller 方法。 


方法postHandle:由preHandle 方法的解釋咱們知道這個方法包括後面要說到的afterCompletion 方法都只能是在當前所屬的Interceptor 的preHandle 方法的返回值爲true 時才能被調用。postHandle 方法,顧名思義就是在當前請求進行處理以後,也就是Controller 方法調用以後執行,可是它會在DispatcherServlet 進行視圖返回渲染以前被調用,因此咱們能夠在這個方法中對Controller 處理以後的ModelAndView 對象進行操做,好比說設置cookie,返回給前端。postHandle 方法被調用的方向跟preHandle 是相反的,也就是說先聲明的Interceptor 的postHandle 方法反而會後執行


方法afterCompletion:該方法也是須要當前對應的Interceptor 的preHandle 方法的返回值爲true 時纔會執行。顧名思義,該方法將在整個請求結束以後,也就是在DispatcherServlet 渲染了對應的視圖以後執行。這個方法的主要做用是用於進行資源清理工做的。

 

例:

@Component
public class AuthInterceptor implements HandlerInterceptor {
  
  private static final String TOKEN_COOKIE = "token";
  
  
  @Autowired
  private UserDao userDao;

  
  @Override
  public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler)
          throws Exception {
    Map<String, String[]> map = req.getParameterMap();
    map.forEach((k,v) ->req.setAttribute(k, Joiner.on(",").join(v)));
    String requestURI = req.getRequestURI();
    if (requestURI.startsWith("/static") || requestURI.startsWith("/error")) {
      return true;
    }
    Cookie cookie = WebUtils.getCookie(req, TOKEN_COOKIE);
    if (cookie != null && StringUtils.isNoneBlank(cookie.getValue())) {
        User user = userDao.getUserByToken(cookie.getValue());
        if (user != null) {
          req.setAttribute(CommonConstants.LOGIN_USER_ATTRIBUTE, user);

          UserContext.setUser(user);
        }
    }
    return true;
  }
  

  @Override
  public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler,
          ModelAndView modelAndView) throws Exception {
    String requestURI = req.getRequestURI();
    if (requestURI.startsWith("/static") || requestURI.startsWith("/error")) {
      return ;
    }
    User user = UserContext.getUser();
    if (user != null && StringUtils.isNoneBlank(user.getToken())) {
       String token = requestURI.startsWith("logout")? "" : user.getToken();
       Cookie cookie = new Cookie(TOKEN_COOKIE, token);
       cookie.setPath("/");
       cookie.setHttpOnly(false);
       res.addCookie(cookie);
    }
    
  }

  @Override
  public void afterCompletion(HttpServletRequest req, HttpServletResponse response, Object handler, Exception ex)
          throws Exception {
    UserContext.remove();
  }
}
相關文章
相關標籤/搜索