亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

ThreadLocal使用案例_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

 更新時(shí)間:2017年08月04日 10:38:40   投稿:mrr  
這篇文章主要介紹了ThreadLocal使用案例分析,需要的朋友可以參考下

用戶(hù)提出一個(gè)需求:當(dāng)修改產(chǎn)品價(jià)格的時(shí)候,需要記錄操作日志,什么時(shí)候做了什么事情。

想必這個(gè)案例,只要是做過(guò)應(yīng)用系統(tǒng)的小伙伴們,都應(yīng)該遇到過(guò)吧?無(wú)外乎數(shù)據(jù)庫(kù)里就兩張表:product 與 log,用兩條 SQL 語(yǔ)句應(yīng)該可以解決問(wèn)題:

update product set price = ? where id = ?
insert into log (created, description) values (?, ?)

But!要確保這兩條 SQL 語(yǔ)句必須在同一個(gè)事務(wù)里進(jìn)行提交,否則有可能 update 提交了,但 insert 卻沒(méi)有提交。如果這樣的事情真的發(fā)生了,我們肯定會(huì)被用戶(hù)指著鼻子狂罵:“為什么產(chǎn)品價(jià)格改了,卻看不到什么時(shí)候改的呢?”。
聰明的我在接到這個(gè)需求以后,是這樣做的:

首先,我寫(xiě)一個(gè) DBUtil 的工具類(lèi),封裝了數(shù)據(jù)庫(kù)的常用操作: 

public class DBUtil {
 // 數(shù)據(jù)庫(kù)配置
 private static final String driver = "com.mysql.jdbc.Driver";
 private static final String url = "jdbc:mysql://localhost:3306/demo";
 private static final String username = "root";
 private static final String password = "root";
 // 定義一個(gè)數(shù)據(jù)庫(kù)連接
 private static Connection conn = null;
 // 獲取連接
 public static Connection getConnection() {
  try {
   Class.forName(driver);
   conn = DriverManager.getConnection(url, username, password);
  } catch (Exception e) {
   e.printStackTrace();
  }
  return conn;
 }
 // 關(guān)閉連接
 public static void closeConnection() {
  try {
   if (conn != null) {
    conn.close();
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

里面搞了一個(gè) static 的 Connection,這下子數(shù)據(jù)庫(kù)連接就好操作了,牛逼吧!

然后,我定義了一個(gè)接口,用于給邏輯層來(lái)調(diào)用:

public interface ProductService {
 void updateProductPrice(long productId, int price);
}

根據(jù)用戶(hù)提出的需求,我想這個(gè)接口完全夠用了。根據(jù) productId 去更新對(duì)應(yīng) Product 的 price,然后再插入一條數(shù)據(jù)到 log 表中。

其實(shí)業(yè)務(wù)邏輯也不太復(fù)雜,于是我快速地完成了 ProductService 接口的實(shí)現(xiàn)類(lèi):

public class ProductServiceImpl implements ProductService {
 private static final String UPDATE_PRODUCT_SQL = "update product set price = ? where id = ?";
 private static final String INSERT_LOG_SQL = "insert into log (created, description) values (?, ?)";
 public void updateProductPrice(long productId, int price) {
  try {
   // 獲取連接
   Connection conn = DBUtil.getConnection();
   conn.setAutoCommit(false); // 關(guān)閉自動(dòng)提交事務(wù)(開(kāi)啟事務(wù))
   // 執(zhí)行操作
   updateProduct(conn, UPDATE_PRODUCT_SQL, productId, price); // 更新產(chǎn)品
   insertLog(conn, INSERT_LOG_SQL, "Create product."); // 插入日志
   // 提交事務(wù)
   conn.commit();
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   // 關(guān)閉連接
   DBUtil.closeConnection();
  }
 }
 private void updateProduct(Connection conn, String updateProductSQL, long productId, int productPrice) throws Exception {
  PreparedStatement pstmt = conn.prepareStatement(updateProductSQL);
  pstmt.setInt(1, productPrice);
  pstmt.setLong(2, productId);
  int rows = pstmt.executeUpdate();
  if (rows != 0) {
   System.out.println("Update product success!");
  }
 }
 private void insertLog(Connection conn, String insertLogSQL, String logDescription) throws Exception {
  PreparedStatement pstmt = conn.prepareStatement(insertLogSQL);
  pstmt.setString(1, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()));
  pstmt.setString(2, logDescription);
  int rows = pstmt.executeUpdate();
  if (rows != 0) {
   System.out.println("Insert log success!");
  }
 }
}

代碼的可讀性還算不錯(cuò)吧?這里我用到了 JDBC 的高級(jí)特性 Transaction 了。暗自慶幸了一番之后,我想是不是有必要寫(xiě)一個(gè)客戶(hù)端,來(lái)測(cè)試一下執(zhí)行結(jié)果是不是我想要的呢? 于是我偷懶,直接在 ProductServiceImpl 中增加了一個(gè) main() 方法:

public static void main(String[] args) {
 ProductService productService = new ProductServiceImpl();
 productService.updateProductPrice(1, 3000);
}

我想讓 productId 為 1 的產(chǎn)品的價(jià)格修改為 3000。于是我把程序跑了一遍,控制臺(tái)輸出:

Update product success!
Insert log success!

應(yīng)該是對(duì)了。作為一名專(zhuān)業(yè)的程序員,為了萬(wàn)無(wú)一失,我一定要到數(shù)據(jù)庫(kù)里在看看。沒(méi)錯(cuò)!product 表對(duì)應(yīng)的記錄更新了,log 表也插入了一條記錄。這樣就可以將 ProductService 接口交付給別人來(lái)調(diào)用了。

幾個(gè)小時(shí)過(guò)去了,QA 妹妹開(kāi)始罵我:“我靠!我才模擬了 10 個(gè)請(qǐng)求,你這個(gè)接口怎么就掛了?說(shuō)是數(shù)據(jù)庫(kù)連接關(guān)閉了!”。

聽(tīng)到這樣的叫聲,讓我渾身打顫,立馬中斷了我的小視頻,趕緊打開(kāi) IDE,找到了這個(gè) ProductServiceImpl 這個(gè)實(shí)現(xiàn)類(lèi)。好像沒(méi)有 Bug 吧?但我現(xiàn)在不敢給她任何回應(yīng),我確實(shí)有點(diǎn)怕她的。

我突然想起,她是用工具模擬的,也就是模擬多個(gè)線(xiàn)程了!那我自己也可以模擬啊,于是我寫(xiě)了一個(gè)線(xiàn)程類(lèi):

public class ClientThread extends Thread {
 private ProductService productService;
 public ClientThread(ProductService productService) {
  this.productService = productService;
 }
 @Override
 public void run() {
  System.out.println(Thread.currentThread().getName());
  productService.updateProductPrice(1, 3000);
 }
}

我用這線(xiàn)程去調(diào)用 ProduceService 的方法,看看是不是有問(wèn)題。此時(shí),我還要再修改一下 main() 方法:

// public static void main(String[] args) {
//  ProductService productService = new ProductServiceImpl();
//  productService.updateProductPrice(1, 3000);
// }
public static void main(String[] args) {
 for (int i = 0; i < 10; i++) {
  ProductService productService = new ProductServiceImpl();
  ClientThread thread = new ClientThread(productService);
  thread.start();
 }
}

我也模擬 10 個(gè)線(xiàn)程吧,我就不信那個(gè)邪了!

運(yùn)行結(jié)果真的讓我很暈、很暈:

Thread-1
Thread-3
Thread-5
Thread-7
Thread-9
Thread-0
Thread-2
Thread-4
Thread-6
Thread-8
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:411)
at com.mysql.jdbc.Util.getInstance(Util.java:386)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1015)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:989)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:975)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:920)
at com.mysql.jdbc.ConnectionImpl.throwConnectionClosedException(ConnectionImpl.java:1304)
at com.mysql.jdbc.ConnectionImpl.checkClosed(ConnectionImpl.java:1296)
at com.mysql.jdbc.ConnectionImpl.commit(ConnectionImpl.java:1699)
at com.smart.sample.test.transaction.solution1.ProductServiceImpl.updateProductPrice(ProductServiceImpl.java:25)
at com.smart.sample.test.transaction.ClientThread.run(ClientThread.java:18)

我靠!竟然在多線(xiàn)程的環(huán)境下報(bào)錯(cuò)了,果然是數(shù)據(jù)庫(kù)連接關(guān)閉了。怎么回事呢?我陷入了沉思中。于是我 Copy 了一把那句報(bào)錯(cuò)信息,在百度、Google,還有 OSC 里都找了,解答實(shí)在是千奇百怪。

我突然想起,既然是跟 Connection 有關(guān)系,那我就將主要精力放在檢查 Connection 相關(guān)的代碼上吧。是不是 Connection 不應(yīng)該是 static 的呢?我當(dāng)初設(shè)計(jì)成 static 的主要是為了讓 DBUtil 的 static 方法訪(fǎng)問(wèn)起來(lái)更加方便,用 static 變量來(lái)存放 Connection 也提高了性能啊。怎么搞呢?

原來(lái)要使每個(gè)線(xiàn)程都擁有自己的連接,而不是共享同一個(gè)連接,否則線(xiàn)程1有可能會(huì)關(guān)閉線(xiàn)程2的連接,所以線(xiàn)程2就報(bào)錯(cuò)了。一定是這樣!

我趕緊將 DBUtil 給重構(gòu)了:

public class DBUtil {
 // 數(shù)據(jù)庫(kù)配置
 private static final String driver = "com.mysql.jdbc.Driver";
 private static final String url = "jdbc:mysql://localhost:3306/demo";
 private static final String username = "root";
 private static final String password = "root";
 // 定義一個(gè)用于放置數(shù)據(jù)庫(kù)連接的局部線(xiàn)程變量(使每個(gè)線(xiàn)程都擁有自己的連接)
 private static ThreadLocal<Connection> connContainer = new ThreadLocal<Connection>();
 // 獲取連接
 public static Connection getConnection() {
  Connection conn = connContainer.get();
  try {
   if (conn == null) {
    Class.forName(driver);
    conn = DriverManager.getConnection(url, username, password);
   }
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   connContainer.set(conn);
  }
  return conn;
 }
 // 關(guān)閉連接
 public static void closeConnection() {
  Connection conn = connContainer.get();
  try {
   if (conn != null) {
    conn.close();
   }
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   connContainer.remove();
  }
 }
}

我把 Connection 放到了 ThreadLocal 中,這樣每個(gè)線(xiàn)程之間就隔離了,不會(huì)相互干擾了。

此外,在 getConnection() 方法中,首先從 ThreadLocal 中(也就是 connContainer 中) 獲取 Connection,如果沒(méi)有,就通過(guò) JDBC 來(lái)創(chuàng)建連接,最后再把創(chuàng)建好的連接放入這個(gè) ThreadLocal 中??梢园?nbsp;ThreadLocal 看做是一個(gè)容器,一點(diǎn)不假。

同樣,我也對(duì) closeConnection() 方法做了重構(gòu),先從容器中獲取 Connection,拿到了就 close 掉,最后從容器中將其 remove 掉,以保持容器的清潔。

這下應(yīng)該行了吧?我再次運(yùn)行 main() 方法:

Thread-0
Thread-2
Thread-4
Thread-6
Thread-8
Thread-1
Thread-3
Thread-5
Thread-7
Thread-9
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!
Update product success!
Insert log success!

我去!總算是解決了,QA 妹妹,你應(yīng)該會(huì)對(duì)我微笑一下吧?

感謝您的關(guān)注,分享是一種快樂(lè),也希望得到您的支持與批評(píng)!

注意:該示例僅用于說(shuō)明 TheadLocal 的基本用法。在實(shí)際工作中,推薦使用連接池來(lái)管理數(shù)據(jù)庫(kù)連接。示例中的代碼僅作參考,使用前請(qǐng)酌情考慮。

相關(guān)文章

  • Spring 動(dòng)態(tài)代理實(shí)現(xiàn)代碼實(shí)例

    Spring 動(dòng)態(tài)代理實(shí)現(xiàn)代碼實(shí)例

    這篇文章主要介紹了Spring 動(dòng)態(tài)代理實(shí)現(xiàn)代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • java中如何判斷對(duì)象是否是垃圾

    java中如何判斷對(duì)象是否是垃圾

    這篇文章主要介紹了java中如何判斷對(duì)象是否是垃圾,Java有兩種算法判斷對(duì)象是否是垃圾:引用計(jì)數(shù)算法和可達(dá)性分析算法,需要的朋友可以參考下
    2023-04-04
  • Java的函數(shù)方法詳解(含漢諾塔問(wèn)題)

    Java的函數(shù)方法詳解(含漢諾塔問(wèn)題)

    漢諾塔問(wèn)題是一個(gè)經(jīng)典的遞歸問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于Java函數(shù)方法(含漢諾塔問(wèn)題)的相關(guān)資料,文中通過(guò)圖文以及代碼示例介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • 淺析Spring4新特性概述

    淺析Spring4新特性概述

    Spring 4.1并沒(méi)有特別吸引眼球的地方,主要還是增強(qiáng)和一些依賴(lài)的版本升級(jí)。本文重點(diǎn)給大家介紹Spring4新特性概述,感興趣的朋友一起看看吧
    2017-09-09
  • Java 實(shí)現(xiàn)攔截器Interceptor的攔截功能方式

    Java 實(shí)現(xiàn)攔截器Interceptor的攔截功能方式

    這篇文章主要介紹了Java 實(shí)現(xiàn)攔截器Interceptor的攔截功能方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • Java中while循環(huán)的使用方法舉例詳解

    Java中while循環(huán)的使用方法舉例詳解

    在Java編程語(yǔ)言中,while循環(huán)是基礎(chǔ)控制結(jié)構(gòu)之一,用于重復(fù)執(zhí)行代碼塊直至滿(mǎn)足特定條件,掌握其使用是編程的基礎(chǔ),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-09-09
  • Linux系統(tǒng)Java環(huán)境配置教程

    Linux系統(tǒng)Java環(huán)境配置教程

    這篇文章給大家介紹的Linux 系統(tǒng)Java環(huán)境配置教程,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2018-05-05
  • Java Stream reduce()使用指南

    Java Stream reduce()使用指南

    reduce()是Java Stream API中的一個(gè)重要終端操作,用于將流中的元素通過(guò)二元運(yùn)算符結(jié)合起來(lái),生成單一結(jié)果,它主要用于計(jì)算總和、乘積、最大值、最小值和字符串連接等,本文給大家介紹Java Stream reduce(),感興趣的朋友一起看看吧
    2024-10-10
  • 實(shí)例講解Java設(shè)計(jì)模式編程中如何運(yùn)用代理模式

    實(shí)例講解Java設(shè)計(jì)模式編程中如何運(yùn)用代理模式

    這篇文章主要介紹了Java設(shè)計(jì)模式編程中如何運(yùn)用代理模式,文中舉了普通代理和強(qiáng)制代理的例子作為代理模式的擴(kuò)展內(nèi)容,需要的朋友可以參考下
    2016-02-02
  • Java中filter用法完整代碼示例

    Java中filter用法完整代碼示例

    這篇文章主要介紹了Java中filter用法完整代碼示例,分享了瀏覽器不緩存頁(yè)面的過(guò)濾器,檢測(cè)用戶(hù)是否登陸的過(guò)濾器等相關(guān)實(shí)例,小編覺(jué)得還是挺不錯(cuò)的,這里分享給大家,需要的朋友可以參考下
    2018-01-01

最新評(píng)論