摘自:http://topmanopensource.iteye.com/blog/1738178
進行多線程編程,同步控制是非常重要的,而同步控制就涉及到了鎖。
對代碼進行同步控制我們可以選擇同步方法,也可以選擇同步塊,這兩種方式各有優缺點,至於具體選擇什么方式,就見仁見智了,同步塊不僅可以更加精確的控制對象鎖,也就是控制鎖的作用域,何謂鎖的作用域?鎖的作用域就是從鎖被獲取到其被釋放的時間。而且可以選擇要獲取哪個對象的對象鎖。但是如果在使用同步塊機制時,如果使用過多的鎖也會容易引起死鎖問題,同時獲取和釋放所也有代價,而同步方法,它們所擁有的鎖就是該方法所屬的類的對象鎖,換句話說,也就是this對象,而且鎖的作用域也是整個方法,這可能導致其鎖的作用域可能太大,也有可能引起死鎖,同時因為可能包含了不需要進行同步的代碼塊在內,也會降低程序的運行效率。而不管是同步方法還是同步塊,我們都不應該在他們的代碼塊內包含無限循環,如果代碼內部要是有了無限循環,那么這個同步方法或者同步塊在獲取鎖以后因為代碼會一直不停的循環着運行下去,也就沒有機會釋放它所獲取的鎖,而其它等待這把鎖的線程就永遠無法獲取這把鎖,這就造成了一種死鎖現象。
詳細解說一下同步方法的鎖,同步方法分為靜態同步方法與非靜態同步方法。
所有的非靜態同步方法用的都是同一把鎖——實例對象本身,也就是說如果一個實例對象的非靜態同步方法獲取鎖后,該實例對象的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖后才能獲取鎖,可是別的實例對象的非靜態同步方法因為跟該實例對象的非靜態同步方法用的是不同的鎖,所以毋須等待該實例對象已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖。
而所有的靜態同步方法用的也是同一把鎖——類對象本身,這兩把鎖是兩個不同的對象,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的。但是一旦一個靜態同步方法獲取鎖后,其他的靜態同步方法都必須等待該方法釋放鎖后才能獲取鎖,而不管是同一個實例對象的靜態同步方法之間,還是不同的實例對象的靜態同步方法之間,只要它們同一個類的實例對象!
而對於同步塊,由於其鎖是可以選擇的,所以只有使用同一把鎖的同步塊之間才有着競態條件,這就得具體情況具體分析了,但這里有個需要注意的地方,同步塊的鎖是可以選擇的,但是不是可以任意選擇的!!!!這里必須要注意一個物理對象和一個引用對象的實例變量之間的區別!使用一個引用對象的實例變量作為鎖並不是一個好的選擇,因為同步塊在執行過程中可能會改變它的值,其中就包括將其設置為null,而對一個null對象加鎖會產生異常,並且對不同的對象加鎖也違背了同步的初衷!這看起來是很清楚的,但是一個經常發生的錯誤就是選用了錯誤的鎖對象,因此必須注意:同步是基於實際對象而不是對象引用的!多個變量可以引用同一個對象,變量也可以改變其值從而指向其他的對象,因此,當選擇一個對象鎖時,我們要根據實際對象而不是其引用來考慮!作為一個原則,不要選擇一個可能會在鎖的作用域中改變值的實例變量作為鎖對象!!!!
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-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
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; } }
效果是完全一樣的。
public static synchronized int setName(String name){ Xxx.name = name; }
等價於
public static int setName(String name){ synchronized(Xxx.class){ Xxx.name = name; } }
public class NameList { private List nameList = Collections.synchronizedList(new LinkedList()); public void add(String name) { nameList.add(name); } public String removeFirst() { if (nameList.size() > 0) { return (String) nameList.remove(0); } else { return null; } } } public class Test { public static void main(String[] args) { final NameList nl = new NameList(); nl.add("aaa"); class NameDropper extends Thread{ public void run(){ String name = nl.removeFirst(); System.out.println(name); } } Thread t1 = new NameDropper(); Thread t2 = new NameDropper(); t1.start(); t2.start(); } }
public class NameList { private List nameList = Collections.synchronizedList(new LinkedList()); public synchronized void add(String name) { nameList.add(name); } public synchronized String removeFirst() { if (nameList.size() > 0) { return (String) nameList.remove(0); } else { return null; } } }
public class DeadlockRisk { private static class Resource { public int value; } private Resource resourceA = new Resource(); private Resource resourceB = new Resource(); public int read() { synchronized (resourceA) { synchronized (resourceB) { return resourceB.value + resourceA.value; } } } public void write(int a, int b) { synchronized (resourceB) { synchronized (resourceA) { resourceA.value = a; resourceB.value = b; } } } }
假設read()方法由一個線程啟動,write()方法由另外一個線程啟動。讀線程將擁有resourceA鎖,寫線程將擁有resourceB鎖,兩者都堅持等待的話就出現死鎖。
- package com.etrip.concurrent.executor;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.Map;
- import java.util.Map.Entry;
- import java.util.Set;
- /**
- * 非靜態同步方法,靜態同步方法,同步語句塊的使用
- *
- *
- * 進行多線程編程,同步控制是非常重要的,而同步控制就涉及到了鎖。
- 對代碼進行同步控制我們可以選擇同步方法,也可以選擇同步塊,這兩種方式各有優缺點,至於具體選擇什么方式,就見仁見智了,同步塊不僅可以更加精確的控制對象鎖,也就是控制鎖的作用域,何謂鎖的作用域?鎖的作用域就是從鎖被獲取到其被釋放的時間。而且可以選擇要獲取哪個對象的對象鎖。但是如果在使用同步塊機制時,如果使用過多的鎖也會容易引起死鎖問題,同時獲取和釋放所也有代價,而同步方法,它們所擁有的鎖就是該方法所屬的類的對象鎖,換句話說,也就是this對象,而且鎖的作用域也是整個方法,這可能導致其鎖的作用域可能太大,也有可能引起死鎖,同時因為可能包含了不需要進行同步的代碼塊在內,也會降低程序的運行效率。而不管是同步方法還是同步塊,我們都不應該在他們的代碼塊內包含無限循環,如果代碼內部要是有了無限循環,那么這個同步方法或者同步塊在獲取鎖以后因為代碼會一直不停的循環着運行下去,也就沒有機會釋放它所獲取的鎖,而其它等待這把鎖的線程就永遠無法獲取這把鎖,這就造成了一種死鎖現象。
- *
- * @author longgangbai
- */
- public class StaticInstanceLock {
- private int count;
- private static StaticInstanceLock instance=null;
- private StaticInstanceLock(){
- }
- /**
- * 靜態方法的鎖
- *
- * @return
- */
- public static synchronized StaticInstanceLock getInstance(){
- if(instance==null){
- instance=new StaticInstanceLock();
- }
- return instance;
- }
- /**
- * 非靜態方法的鎖
- * @return
- */
- public synchronized int getCount(){
- return count;
- }
- public synchronized void setCount(int count){
- this.count=count;
- }
- /**
- * 同步語句塊的使用
- *
- */
- public void synmethod(){
- //HashMap為非安全性Map
- HashMap<String,String> hashmap = new HashMap<String,String>();
- hashmap.put("ZH","中國");
- hashmap.put("EN","英國");
- hashmap.put("AM","美國");
- hashmap.put("FR","法國");
- //創建一個同步的對象Map
- Map<String,String> m = Collections.synchronizedMap(hashmap);
- Set<String> s = m.keySet(); // Needn't be in synchronized block
- //這里同步的對象均為需要使用同步的對象如Map而非Set
- synchronized(m) { // Synchronizing on m, not s!
- Iterator<String> i = s.iterator(); // Must be in synchronized block
- while (i.hasNext()){
- foo(i.next());
- }
- }
- }
- public void foo(String entry){
- System.out.println("StaticInstanceLock ="+entry);
- }
- public static void main(String[] args) {
- StaticInstanceLock instance=StaticInstanceLock.getInstance();
- instance.setCount(7);
- int count = instance.getCount();
- instance.synmethod();
- }
- }