一、數據庫事務基礎知識java
1)數據庫事務有嚴格的定義,它必須同時知足4個特性:原子性(Atomic)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability),簡稱ACID。
2)數據併發的問題:髒讀、不可重複讀、幻想讀、第一類丟失更新、第二類丟失更新。
3)數據庫鎖機制:
按鎖定的對象的不一樣:通常能夠分別表鎖定和行鎖定,前者對整個表進行鎖定,然後者對錶中特定行進行鎖定。從併發事務鎖定的關係上看,能夠分爲共享鎖定和獨佔鎖定。共享鎖定會防止獨佔鎖定,但容許其餘的共享鎖定。而獨佔鎖定既防止其餘的獨佔鎖定,也防止其餘的共享鎖定。爲了更改數據,數據庫必須在進行更改的行上施加行獨佔鎖定,INSERT、UPDATE、DELETE和SELECT FOR UPDATE語句都會隱式採用必要的行鎖定。
4)事務隔離級別:READ UNCOMMITED 、 READ COMMITTED、REPEATABLE READ、SERIALIZABLE。
5)JDBC對事務支持
用戶能夠經過Connection#getMetaData()方法獲取DatabaseMetaData對象,並經過該對象的supportsTransactions()、supportsTransactionIsolationLevel(int level)方法查看底層數據庫的事務支持狀況。Connection默認狀況下是自動提交的,也即每條執行的SQL都對應一個事務,爲了可以將多條SQL當成一個事務執行,必須先經過Connection#setAutoCommit(false)阻止Connection自動提交,並可經過Connection#setTransactionIsolation()設置事務的隔離級別,Connection中定義了對應SQL92標準4個事務隔離級別的常量。經過Connection#commit()提交事務,經過Connection#rollback()回滾事務。
典型的JDBC事務數據操做的代碼:
Connection conn;
try{
conn = DriverManager.getConnection();
conn.setAutoCommit(false);
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
Statement stmt = conn.createStatement();
int rows = stmt.executeUpdate("INSERT INTO t_topic VALUES(1,'tom')");
rows = stmt.executeUpdate("UPDATE t_user set topic_nums = topic_nums + 1 WHERE user_id = 1");
conn.commit();
}catch(Exception e){
...
conn.rollback();
}finally(
...
)
二、ThreadLocal基礎知識
ThreadLocal,顧名思義,他不是一個線程,而是線程的一個本地化對象。當工做於多線程中的對象使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程分配一個獨立的變量副本。因此每個線程均可以獨立地改變本身的副本,而不會影響其餘線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量,這也是類名中「Local」所要表達的意思。
ThreadLocal類接口很簡單,只有4個方法:
1)void set(Object value):設置當前線程的線程局部變量的值;
2)public Object get():該方法返回當前線程所對應的線程局部變量;
3)public void remove():將當前線程局部變量的值刪除,目的是爲了減小內存的利用。
4)protected Object initialValue():返回該線程局部變量的初始值。
ThreadLocal是如何作到爲每個線程維護一份獨立的變量副本呢?其實實現的思路很簡單:在ThreadLocal類中有一個Map,用於存儲每個線程的變量副本,Map中元素鍵爲線程對象,而值對應線程的變量副本。
簡單的實現版本:
package com.yyq.transaction;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class SimpleThreadLocal {
private Map valueMap = Collections.synchronizedMap(new HashMap());
public void set(Object newValue){
valueMap.put(Thread.currentThread(),newValue);
}
public Object get(){
Thread currentThread = Thread.currentThread();
Object o = valueMap.get(currentThread);
if (o == null && !valueMap.containsKey(currentThread)){
o = initialValue();
valueMap.put(currentThread,o);
}
return o;
}
public void remove(){
valueMap.remove(Thread.currentThread());
}
public Object initialValue(){
return null;
}
}
一個ThreadLocal實例:數據庫
package com.yyq.transaction;
public class SequenceNumber {
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
public Integer initialValue() {
return 0;
}
};
public int getNextNum() {
seqNum.set(seqNum.get() + 1);
return seqNum.get();
}
private static class TestClient extends Thread {
private SequenceNumber sn;
public TestClient(SequenceNumber sn) {
this.sn = sn;
}
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("thread[" + Thread.currentThread().getName() + "]sn[" + sn.getNextNum() + "]");
}
}
}
public static void main(String[] args) {
SequenceNumber sn = new SequenceNumber();
TestClient t1 = new TestClient(sn);
TestClient t2 = new TestClient(sn);
TestClient t3 = new TestClient(sn);
t1.start();
t2.start();
t3.start();
}
}
輸出結果:
thread[Thread-1]sn[1]
thread[Thread-1]sn[2]
thread[Thread-1]sn[3]
thread[Thread-0]sn[1]
thread[Thread-0]sn[2]
thread[Thread-0]sn[3]
thread[Thread-2]sn[1]
thread[Thread-2]sn[2]
thread[Thread-2]sn[3]
根據輸出結果能夠發現每一個線程所產生的序號雖然都共享同一個SequenceNumber實例,但它們並無發生相互干擾的狀況,而是各自產生獨立的序列號,這是由於咱們經過ThreadLocal爲每個線程提供了單獨的副本。
在同步機制中,經過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序縝密地分析何時對變量進行讀寫,何時須要鎖定某個對象,何時釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。
而ThreadLocal則從另外一個角度來解決多線程的併發訪問。ThreadLocal爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對訪問數據的衝突。由於每個線程都擁有本身的變量副本,從而也就沒有必要對變量進行同步了。ThreadLocal提供了線程安全的對象封裝,在編寫多線程代碼時,能夠把不安全的變量封裝進ThreadLocal。
概況起來講,對於多線程資源共享的問題,同步機制採用了「以時間換空間」的方式:訪問串行化,對象共享化。而ThreadLocal採用了「以空間換時間」的方式:訪問並行化,對象獨享化。前者僅提供一份變量,讓不一樣的線程排隊訪問,然後者爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。