1. 程式人生 > >java執行緒同步的幾種方式

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

當然ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是為了同步多個執行緒對相同資源的併發訪問,是為了多個執行緒之間進行通訊的有效方式;而ThreadLocal是隔離多個執行緒的資料共享,從根本上就不在多個執行緒之間共享資源(變數),這樣當然不需要對多個執行緒進行同步了。所以,如果你需要進行多個執行緒之間進行通訊,則使用同步機制;如果需要隔離多個執行緒之間的共享衝突,可以使用ThreadLocal,這將極大地簡化你的程式,使程式更加易讀、簡潔。

下面的例項能夠體現Spring對有狀態Bean的改造思路:

程式碼清單3 TopicDao:非執行緒安全

public class TopicDao {

private Connection conn;一個非執行緒安全的變數

public void addTopic(){

Statement stat = conn.createStatement();引用非執行緒安全變數

}

}

由於處的conn是成員變數,因為addTopic()方法是非執行緒安全的,必須在使用時建立一個新TopicDao例項(非singleton)。下面使用ThreadLocalconn這個非執行緒安全的狀態進行改造:

程式碼清單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共享了。

當然,這個例子本身很粗糙,將ConnectionThreadLocal直接放在DAO只能做到本DAO的多個方法共享Connection時不發生執行緒安全問題,但無法和其它DAO共用同一個Connection,要做到同一事務多DAO共享同一Connection,必須在一個共同的外部類使用ThreadLocal儲存Connection。但這個例項基本上說明了Spring對有狀態類執行緒安全化的解決思路。