㈠ java多線程並發去調用一個類的靜態方法,有什麼問題
總的結論:java是線程安全的,即對任何方法(包括靜態方法)都可以不考慮線程沖突,但有一個前提,就是不能存在全局變數。如果存在全局變數,則需要使用同步機制。x0dx0ax0dx0a如下通過一組對比例子從頭講解:x0dx0a 在多線程中使用靜態方法會發生什麼事?也就是說多線程訪問同一個類的static靜態方法會發生什麼事?是否會發生線程安全問題?x0dx0apublic class Test {x0dx0a public static void operation(){x0dx0a // ... do somethingx0dx0a }x0dx0a}x0dx0a 事實證明只要在靜態函數中沒有處理多線程共享數據,就不存在著多線程訪問同一個靜態方法會出現資源沖突的問題。下面看一個例子:x0dx0apublic class StaticThread implements Runnable {x0dx0a @Overridex0dx0a public void run() {x0dx0a // TODO Auto-generated method stubx0dx0a StaticAction.print();x0dx0a }x0dx0a public static void main(String[] args) {x0dx0a for (int i = 0; i < 100; i++) {x0dx0a new Thread(new StaticThread()).start();x0dx0a }x0dx0a }x0dx0a}x0dx0apublic class StaticAction {x0dx0a public static int i = 0;x0dx0a public static void print() {x0dx0a int sum = 0;x0dx0a for (int i = 0; i < 10; i++) {x0dx0a System.out.print("step " + i + " is running.");x0dx0a sum += i;x0dx0a }x0dx0a if (sum != 45) {x0dx0a System.out.println("Thread error!");x0dx0a System.exit(0);x0dx0a }x0dx0a System.out.println("sum is " + sum);x0dx0a }x0dx0a}x0dx0a 實際執行的結果顯示各個線程對靜態方法的訪問是交叉執行的,但是這並不影響各個線程靜態方法print()中sum值的計算。也就是說,在此過程中沒有使用全局變數的靜態方法在多線程中是安全的,靜態方法是否引起線程安全問題主要看該靜態方法是否對全局變數(靜態變數static member)進行修改操作。x0dx0a 在多線程中使用同一個靜態方法時,每個線程使用各自的實例欄位(instance field)的副本,而共享一個靜態欄位(static field)。所以說,如果該靜態方法不去操作一個靜態成員,只在方法內部使用實例欄位(instance field),不會引起安全性問題。x0dx0a 但是,如果該靜態方法操作了一個靜態變數,則需要靜態方法中採用互斥訪問的方式進行安全處理。我們來看一下沒有使用互斥訪問的話會產生怎樣的問題:public class StaticAction {x0dx0a public static int i = 0;x0dx0a public static void incValue() {x0dx0a int temp = StaticAction.i;x0dx0a try {x0dx0a Thread.sleep(1);x0dx0a } catch (Exception e) {x0dx0a e.printStackTrace();x0dx0a }x0dx0a temp++;x0dx0a StaticAction.i = temp;x0dx0a }x0dx0a}x0dx0apublic class StaticThread implements Runnable {x0dx0a @Overridex0dx0a public void run() {x0dx0a // TODO Auto-generated method stubx0dx0a StaticAction.incValue();x0dx0a }x0dx0a public static void main(String[] args) {x0dx0a for (int i = 0; i < 100; i++) {x0dx0a new Thread(new StaticThread()).start();x0dx0a }x0dx0a try {x0dx0a Thread.sleep(1000); //預留足夠的時間讓上面的線程跑完x0dx0a } catch (Exception e) {x0dx0a e.printStackTrace();x0dx0a }x0dx0a System.out.println(StaticAction.i);x0dx0a }x0dx0a}x0dx0a 實際運行結果顯示i值為隨機的數字。為了實現互斥訪問,這時我們需要加入一個synchronized關鍵字。代碼修改如下:x0dx0apublic class StaticAction {x0dx0a public static int i = 0;x0dx0a public synchronized static void incValue() {x0dx0a int temp = StaticAction.i;x0dx0a try {x0dx0a Thread.sleep(1);x0dx0a } catch (Exception e) {x0dx0a e.printStackTrace();x0dx0a }x0dx0a temp++;x0dx0a StaticAction.i = temp;x0dx0a }x0dx0a}x0dx0apublic class StaticThread implements Runnable {x0dx0a @Overridex0dx0a public void run() {x0dx0a // TODO Auto-generated method stubx0dx0a StaticAction.incValue();x0dx0a }x0dx0a public static void main(String[] args) {x0dx0a for (int i = 0; i < 100; i++) {x0dx0a new Thread(new StaticThread()).start();x0dx0a }x0dx0a try {x0dx0a Thread.sleep(1000);x0dx0a } catch (Exception e) {x0dx0a e.printStackTrace();x0dx0a }x0dx0a System.out.println(StaticAction.i);x0dx0a }x0dx0a}x0dx0a 運行結果則必然是100。x0dx0a 加入synchronized關鍵字的靜態方法稱為同步靜態方法。x0dx0a 在訪問同步靜態方法時,會獲取該類的「Class」對象,所以當一個線程進入同步的靜態方法中時,線程監視器獲取類本身的對象鎖,其它線程不能進入這個類的任何靜態同步方法。它不像實例方法,因為多個線程可以同時訪問不同實例同步實例方法。這個其實就是操作系統中的用信號量實現進程的互斥與同步問題,如果涉及在同一個類中有多個靜態方法中處理多線程共享數據的話,那就變成用信號量解決生產者-消費者問題。也就是說,靜態方法是一份臨界資源,對靜態方法的訪問屬於進入臨界區;對靜態變數的修改是一份臨界資源,對靜態變數的修改屬於進入臨界區。