java執行緒同步的幾種方式
1.使用synchronized關鍵字,多執行緒的同步依靠的是物件鎖機制,synchronized關鍵字的背後就是利用了封鎖來實現對共享資源的互斥訪問。
2.使用lock, Lock是java.util.concurrent.locks包下的介面,Lock 實現提供了比使用synchronized 方法和語句可獲得的更廣泛的鎖定操作,它能以更優雅的方式處理執行緒同步問題
下面這段程式碼展示了lock的用法
public class LockTest {
public static void main(String[] args) {
final Outputter1 output = new Outputter1();
new Thread() {
public void run() {
output.output("zhangsan");
};
}.start();
new Thread() {
public void run() {
output.output("lisi");
};
}.start();
}
}
class Outputter1 {
private Lock lock = new ReentrantLock();// 鎖物件
public void output(String name) {
// TODO 執行緒輸出方法
lock.lock();// 得到鎖
try {
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
} finally {
lock.unlock();// 釋放鎖
}
}
}
3.使用threadlocal類
ThreadLocal是解決執行緒安全問題一個很好的思路,ThreadLocal類中有一個Map,用於儲存每一個執行緒的變數副本,Map中元素的鍵為執行緒物件,而值對應執行緒的變數副本,由於Key值不可重複,每一個“執行緒物件”對應執行緒的“變數副本”,而到達了執行緒安全。
在同步機制中,通過物件的鎖機制保證同一時間只有一個執行緒訪問變數。這時該變數是多個執行緒共享的,使用同步機制要求程式慎密地分析什麼時候對變數進行讀寫,什麼時候需要鎖定某個物件,什麼時候釋放物件鎖等繁雜的問題,程式設計和編寫難度相對較大。
而ThreadLocal則從另一個角度來解決多執行緒的併發訪問。
當然ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同步多個執行緒對相同資源的併發訪問,是為了多個執行緒之間進行通訊的有效方式;而ThreadLocal是隔離多個執行緒的資料共享,從根本上就不在多個執行緒之間共享資源(變數),這樣當然不需要對多個執行緒進行同步了。所以,如果你需要進行多個執行緒之間進行通訊,則使用同步機制;如果需要隔離多個執行緒之間的共享衝突,可以使用ThreadLocal,這將極大地簡化你的程式,使程式更加易讀、簡潔。
下面的例項能夠體現Spring對有狀態Bean的改造思路:
程式碼清單3 TopicDao:非執行緒安全
public class TopicDao {
private Connection conn;①一個非執行緒安全的變數
public void addTopic(){
Statement stat = conn.createStatement();②引用非執行緒安全變數
…
}
}
由於①處的conn是成員變數,因為addTopic()方法是非執行緒安全的,必須在使用時建立一個新TopicDao例項(非singleton)。下面使用ThreadLocal對conn這個非執行緒安全的“狀態”進行改造:
程式碼清單4 TopicDao:執行緒安全
import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {
①使用ThreadLocal儲存Connection變數
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();
public static Connection getConnection(){
②如果connThreadLocal沒有本執行緒對應的Connection建立一個新的Connection,
並將其儲存到執行緒本地變數中。
if (connThreadLocal.get() == null) {
Connection conn = ConnectionManager.getConnection();
connThreadLocal.set(conn);
return conn;
}else{
return connThreadLocal.get();③直接返回執行緒本地變數
}
}
public void addTopic() {
④從ThreadLocal中獲取執行緒對應的Connection
Statement stat = getConnection().createStatement();
}
}
不同的執行緒在使用TopicDao時,先判斷connThreadLocal.get()是否是null,如果是null,則說明當前執行緒還沒有對應的Connection物件,這時建立一個Connection物件並新增到本地執行緒變數中;如果不為null,則說明當前的執行緒已經擁有了Connection物件,直接使用就可以了。這樣,就保證了不同的執行緒使用執行緒相關的Connection,而不會使用其它執行緒的Connection。因此,這個TopicDao就可以做到singleton共享了。
當然,這個例子本身很粗糙,將Connection的ThreadLocal直接放在DAO只能做到本DAO的多個方法共享Connection時不發生執行緒安全問題,但無法和其它DAO共用同一個Connection,要做到同一事務多DAO共享同一Connection,必須在一個共同的外部類使用ThreadLocal儲存Connection。但這個例項基本上說明了Spring對有狀態類執行緒安全化的解決思路。