1、一套清晰的規則java
不管是加了鎖的方法仍是加了鎖的代碼塊,不管加的是對象鎖仍是加的是類鎖,只要上的是同一把鎖,那麼它們的訪問規則就應該是相同的。也就是上了同一把鎖的東西,一個線程進來要麼一塊兒容許被訪問,要麼一塊兒禁止訪問。所以,若是想搞清楚訪問的規則,咱們首先要搞清楚鎖的類型。而後判斷,只要上的是同一把鎖,訪問的規則就應該相同。那麼java中的鎖有那些類型呢。能夠簡單的總結爲兩種類型:緩存
一)java中鎖的類型:多線程
1.類鎖:只有synchronized修飾靜態方法或者修飾一個類的chass對象時,纔是類鎖。性能
2.對象鎖:除了類鎖,全部其餘上鎖方式都認爲是對象鎖,好比:synchronized修飾普通方法或者synchronized(this)給代碼塊上鎖。測試
應該注意的是,由於一個類只有一個.class對象,所以全部的訪問者在訪問被加了類鎖的方法或者代碼塊時候,都是共用同一把鎖,而類的實例卻能夠有不少個,所以不一樣對象訪問加了對象鎖的方法或者代碼塊,它們的訪問互不干擾。this
二)知道了鎖的類型,那麼咱們就能夠總結出一個通用且清晰的規則了。以下:線程
1.加了相同鎖的方法或者代碼塊,他們的訪問規則是相同的,即當否個訪問者得到該鎖的時候,他們(上鎖的方法或者代碼塊)一塊兒向訪問者開放(當前訪問者能夠調用這些方法)同時其餘訪問者若是訪問(上相同鎖的這些方法訪問)只能等待。對象
2.加了不一樣鎖的代碼或者代碼塊,多線程訪問互不影響繼承
3.沒有加鎖的方法或者代碼塊能夠隨意訪問不受限制字符串
三)而後再來看怎麼判斷什麼狀況下是相同的鎖。以下:
1.不一樣類型的鎖不是同一把鎖
2.加的是對象鎖,那麼必須是同一對象實例纔是同一把鎖
3.加的是類鎖,那必須是同一類纔是同一把鎖
四)咱們判斷訪問的規則,就是基於個步驟:
1.首先判斷是否是同一把鎖
2.而後判斷各自的訪問規則
五)鎖的重入
鎖重入:好比2個方法one、two都加了synchronized,one裏調用了two,那麼能夠執行two方法(即便one沒有執行完)。繼承也能夠用鎖重入。
2、使用這個規則
1.例子1
//對象鎖
private synchronized void test1(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱們是test1方法。");
}
}
private void test2(){
synchronized(this){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱們是test2方法。");
}
}
}
private void name() {
System.out.println("我沒有鎖,隨便訪問。");
}
public static void main(String arg[]) {
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
sync.test1();
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.name();
sync.test2();
}
});
t1.start();
t2.start();
}
在代碼中,能夠看到咱們給test1方法上了對象鎖,加鎖的方式是給方法加鎖,加的是當前對象鎖;而給test2方法上也上了對象鎖,加鎖的方式給代碼塊加鎖,注意上的是當前對象鎖,你也能夠將this替換成任意其餘對象。而後咱們在main方法中看到,新建了兩個線程,都是用同一個對象來調用這兩個方法。所以,能夠斷定線程t1和t2使用的是同一把鎖,所以訪問規則就是t1得到了這把鎖後,tes1方法和test2中被加鎖的代碼塊都容許t1訪問,都拒絕t2訪問,等t1運行完,t2纔會得到該鎖,進行訪問。所以輸出的結果很明顯了,t1執行完,再執行t2。而sync.name()不受限制,咱們運行下程序,看看咱們按照規則來推理的是否正確,運行結果以下:
0:咱們是test1方法。
我沒有鎖,隨便訪問。
1:咱們是test1方法。
2:咱們是test1方法。
0:咱們是test2方法。
1:咱們是test2方法。
2:咱們是test2方法。
由於t1訪問test1後上個對象鎖,t2這時候也去訪問test2,發現test2也被上了同一個對象的鎖,沒有鑰匙,進不去,只能等t1將對象鎖釋放後才能進去
而後咱們再作個試驗,好比將main方法中的代碼改爲下面的:
private synchronized void test1(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱們是test1方法。");
}
}
private void test2(){
synchronized(this){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱們是test2方法。");
}
}
}
public static void main(String arg[]) {
final SyncSameObjectTest1 st=new SyncSameObjectTest1();
final SyncSameObjectTest1 st2=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
st.test1();
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
st2.test2();
//或者
//st2.test1();
}
});
t1.start();
t2.start();
}
只是多出一個實例而已,而後線程t2經過st2來調用test2。那麼就test1和test2加的都是當前對象的鎖,顯然它們的當前對象不一樣吧。因此它們不是同一把鎖,互相不干擾。那麼咱們運行程序,效果以下:它們各自運行各自的,因此沒什麼順序。
關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼(方法)看成鎖,因此示例代碼中的那個線程先執行synchronized關鍵字的方法,那個線程就持有該方法所屬對象的鎖(Lock),兩個對象,線程得到的就是兩個不一樣的鎖,他們互不影響。
0:咱們是test1方法。
0:咱們是test2方法。
1:咱們是test2方法。
2:咱們是test2方法。
1:咱們是test1方法。
2:咱們是test1方法。
例子2:
//類鎖
private static synchronized void test1(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱們是test1方法。");
}
}
//類鎖
private void test2(){
synchronized(SyncSameObjectTest1.class){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱們是test2方法。");
}
}
}
private void name() {
System.out.println("我沒有鎖,隨便訪問。");
}
public static void main(String arg[]) {
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
SyncSameObjectTest1.test1();
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.name();
sync.test2();
}
});
t1.start();
t2.start();
}
很簡單,test1是一個靜態方法,因此它加的鎖是類鎖, test2也加了一個類鎖,是加在了一個代碼塊中,所以他們加的是相同的鎖。一個類只有一個.class對象,所以全部的訪問者在訪問被加了類鎖的方法或者代碼塊時候,都是共用同一把鎖,即t1訪問完,t2再訪問。
有一種狀況是全部對象都是相同的鎖, 即在靜態方法上加synchronized關鍵字,static方法是類獨有,表示鎖定.class類,類一級別的鎖(獨佔.class類)。而不是對象,這樣就是synchronized的.class類,全部對象都擁有共同的鎖。
運行程序,結果以下:
0:咱們是test1方法。
我沒有鎖,隨便訪問。
1:咱們是test1方法。
2:咱們是test1方法。
0:咱們是test2方法。
1:咱們是test2方法。
2:咱們是test2方法。
例3://類鎖
private static synchronized void test1(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱們是test1方法。");
}
}
//對象鎖
private synchronized void test2(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱們是test2方法。");
}
}
private void name() {
System.out.println("我沒有鎖,隨便訪問。");
}
public static void main(String arg[]) {
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
SyncSameObjectTest1.test1();
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.name();
sync.test2();
}
});
t1.start();
t2.start();
}
代碼中很顯然了,test1是靜態方法,它上的是類鎖。而test2是普通方法,它上的對象鎖。這是不一樣的鎖。因此t1訪問test1時,test2方法是不受干擾的,t2確定能夠同時訪問test2.所以打印順序爲任意順序。以下:
0:咱們是test1方法。
我沒有鎖,隨便訪問。
1:咱們是test1方法。
0:咱們是test2方法。
2:咱們是test1方法。
1:咱們是test2方法。
2:咱們是test2方法。
爲亂序。
此時若是你的打印結果爲先打印出t1的結果再是t2的結果,也沒必要驚訝,由於程序簡單,循環次數少,CPU性能高,因此極可能t2剛啓動就瞬間運行完了t1。
例子3:
//對象鎖
private synchronized void test1(){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱們是test1方法。");
}
}
//類鎖
private void test2(){
synchronized(SyncSameObjectTest1.class){
for (int i = 0; i < 3; i++) {
System.out.println(i+":咱們是test2方法。");
}
}
}
private void name() {
System.out.println("我沒有鎖,隨便訪問。");
}
public static void main(String arg[]) {
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
sync.test1();
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.name();
sync.test2();
}
});
t1.start();
t2.start();
}
很明顯一個類鎖,一個對象鎖,訪問規則互不干擾。即,t1訪問test1方法時,並不影響t2訪問test2(可是會禁止t2訪問test1,由於t2用的也是sync對象,此時t1已給sync對象上鎖了)。因此打印順序可能爲任意順序,還有若是sync同時訪問test2方法,因爲test2是類鎖,因此全部對象共有一把鎖,會按照順序打印。好了,運行程序,結果以下:
0:咱們是test1方法。
我沒有鎖,隨便訪問。
0:咱們是test2方法。
1:咱們是test2方法。
2:咱們是test2方法。
1:咱們是test1方法。
2:咱們是test1方法。
還有不少的實驗咱們就再也不作了,都是根據上面總結的規則來作的。相信這下,你對上鎖和上鎖後的訪問規則都清楚了吧。
3、鎖非this對象 如:(鎖String對象)
在Java中是有常量池緩存的功能的,就是說若是我先聲明瞭一個String str1 = 「a」; 再聲明一個同樣的字符串的時候,取值是從原地址去取的,也就是說是同一個對象。這也就致使了在鎖字符串對象的時候,能夠會取得意料以外的結果(字符串同樣會取得相同鎖),下面看一個例子介紹。都是將「xc」字符串傳給上面的測試方法。下面看下測試結果。
//對象鎖
private void lord(String str){
try {
synchronized(str){
while (true) {
System.out.println(Thread.currentThread().getName());
Thread.sleep(100);
}
}
} catch (Exception e) {
}
}
public static void main(String arg[]) {
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
sync.lord("xc");
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.lord("xc");
}
});
t1.start();
t2.start();
}
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
Thread-0
從結果能夠就看到,線程B並無進來,也就說明兩個線程持有的是同一個鎖,線程1一直循環沒釋放鎖,其餘線程也進不來,即字符串對象是同一個。這就是String常量池會帶來的問題。因此在大多數狀況下,同步代碼塊synchronized代碼塊不使用String做爲鎖對象,而採用其餘。稍加改造,每次都建立一個新的字符串對象,不一樣對象的鎖:
final SyncSameObjectTest1 sync=new SyncSameObjectTest1();
Thread t1=new Thread(new Runnable() {
public void run() {
sync.lord(new String("xc"));
}
});
Thread t2=new Thread(new Runnable() {
public void run() {
sync.lord(new String("xc"));
}
});
t1.start();
t2.start();
}
Thread-0
Thread-1
Thread-1
Thread-0
Thread-1
Thread-0
4、若是你還不理解的話
若是你還不理解的話,這是某位大牛作的一個很好的比喻。你能夠看看。摘抄以下: