Java執行緒:執行緒的同步問題
阿新 • • 發佈:2019-02-01
執行緒的同步是為了防止多個執行緒訪問一個數據物件時,對資料造成的破壞。
例如:兩個執行緒ThreadA、ThreadB都操作同一個物件Foo物件,並修改Foo物件上的資料。
-
public class Foo {
-
private int x = 100;
-
public int getX() {
-
return x;
-
}
-
public int fix(int y) {
-
x = x – y;
-
return x;
-
}
-
}
-
public class MyRunnable implements Runnable {
-
private Foo foo = new Foo();
-
public static void main(String[] args) {
-
MyRunnable r = new MyRunnable();
-
Thread ta = new Thread(r, “Thread-A”);
-
Thread tb = new Thread(r, “Thread-B”);
-
ta.start();
-
tb.start();
-
}
-
public void run() {
-
for (int i = 0; i < 3; i++) {
-
this.fix(30);
-
try {
-
Thread.sleep(1);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
System.out.println(Thread.currentThread().getName() + ” : 當前foo物件的x值= “ + foo.getX());
-
}
-
}
-
public int fix(int y) {
-
return foo.fix(y);
-
}
- }
Thread-A : 當前foo物件的x值= 40
Thread-B : 當前foo物件的x值= 40
Thread-B : 當前foo物件的x值= -20
Thread-A : 當前foo物件的x值= -50
Thread-A : 當前foo物件的x值= -80
Thread-B : 當前foo物件的x值= -80
Process finished with exit code 0
從結果發現,這樣的輸出值明顯是不合理的。原因是兩個執行緒不加控制的訪問Foo物件並修改其資料所致。
如果要保持結果的合理性,只需要達到一個目的,就是將對Foo的訪問加以限制,每次只能有一個執行緒在訪問。這樣就能保證Foo物件中資料的合理性了。
在具體的Java程式碼中需要完成一下兩個操作:
把競爭訪問的資源類Foo變數x標識為private;
同步哪些修改變數的程式碼,使用synchronized關鍵字同步方法或程式碼。
二、同步和鎖定
1、鎖的原理
Java中每個物件都有一個內建鎖
當程式執行到非靜態的synchronized同步方法上時,自動獲得與正在執行程式碼類的當前例項(this例項)有關的鎖。獲得一個物件的鎖也稱為獲取鎖、鎖定物件、在物件上鎖定或在物件上同步。
當程式執行到synchronized同步方法或程式碼塊時才該物件鎖才起作用。
一個物件只有一個鎖。所以,如果一個執行緒獲得該鎖,就沒有其他執行緒可以獲得鎖,直到第一個執行緒釋放(或返回)鎖。這也意味著任何其他執行緒都不能進入該物件上的synchronized方法或程式碼塊,直到該鎖被釋放。
釋放鎖是指持鎖執行緒退出了synchronized同步方法或程式碼塊。
關於鎖和同步,有一下幾個要點:
1)、只能同步方法,而不能同步變數和類;
2)、每個物件只有一個鎖;當提到同步時,應該清楚在什麼上同步?也就是說,在哪個物件上同步?
3)、不必同步類中所有的方法,類可以同時擁有同步和非同步方法。
4)、如果兩個執行緒要執行一個類中的synchronized方法,並且兩個執行緒使用相同的例項來呼叫方法,那麼一次只能有一個執行緒能夠執行方法,另一個需要等待,直到鎖被釋放。也就是說:如果一個執行緒在物件上獲得一個鎖,就沒有任何其他執行緒可以進入(該物件的)類中的任何一個同步方法。
5)、如果執行緒擁有同步和非同步方法,則非同步方法可以被多個執行緒自由訪問而不受鎖的限制。
6)、執行緒睡眠時,它所持的任何鎖都不會釋放。
7)、執行緒可以獲得多個鎖。比如,在一個物件的同步方法裡面呼叫另外一個物件的同步方法,則獲取了兩個物件的同步鎖。
8)、同步損害併發性,應該儘可能縮小同步範圍。同步不但可以同步整個方法,還可以同步方法中一部分程式碼塊。
9)、在使用同步程式碼塊時候,應該指定在哪個物件上同步,也就是說要獲取哪個物件的鎖。例如:
public int fix(int y) {
synchronized (this) {
x = x – y;
}
return x;
}
當然,同步方法也可以改寫為非同步方法,但功能完全一樣的,例如:
public synchronized int getX() {
return x++;
}
與
public int getX() {
synchronized (this) {
return x;
}
}
效果是完全一樣的。
三、靜態方法同步
要同步靜態方法,需要一個用於整個類物件的鎖,這個物件是就是這個類(XXX.class)。
例如:
public static synchronized int setName(String name){
Xxx.name = name;
}
等價於
public static int setName(String name){
synchronized(Xxx.class){
Xxx.name = name;
}
}
四、如果執行緒不能不能獲得鎖會怎麼樣
如果執行緒試圖進入同步方法,而其鎖已經被佔用,則執行緒在該物件上被阻塞。實質上,執行緒進入該物件的的一種池中,必須在哪裡等待,直到其鎖被釋放,該執行緒再次變為可執行或執行為止。
當考慮阻塞時,一定要注意哪個物件正被用於鎖定:
1、呼叫同一個物件中非靜態同步方法的執行緒將彼此阻塞。如果是不同物件,則每個執行緒有自己的物件的鎖,執行緒間彼此互不干預。
2、呼叫同一個類中的靜態同步方法的執行緒將彼此阻塞,它們都是鎖定在相同的Class物件上。
3、靜態同步方法和非靜態同步方法將永遠不會彼此阻塞,因為靜態方法鎖定在Class物件上,非靜態方法鎖定在該類的物件上。
4、對於同步程式碼塊,要看清楚什麼物件已經用於鎖定(synchronized後面括號的內容)。在同一個物件上進行同步的執行緒將彼此阻塞,在不同物件上鎖定的執行緒將永遠不會彼此阻塞。