多線程開發離不開鎖機制,現在的Java語言中,提供了2種鎖,一種是語言特性提供的內置鎖,還有一種是 java.util.concurrent.locks 包中的鎖。對於synchronized的一些基本概念這裏就不重複了,很多博客都寫到了。這篇文章針對一個例子,講一下我的理解。
這是一個調用同步方法的同時,調用該對象非同步方法(但是內部含有同步塊)的例子
package tmp;
/**
* Created by Gracecoder on 2017/11/24.
*/
public class Service {
private String anyString = new String();
public void a(){
try {
synchronized (anyString)
{
System.out.println("a begin");
Thread.sleep(3000);
System.out.println("a end");
}
}catch (Exception e)
{
e.printStackTrace();
}
}
synchronized public void b()
{
System.out.println("b begin");
// synchronized (anyString) {
// anyString = new String("hello");
// System.out.println(anyString);
// }
System.out.println("b end");
}
}
package tmp;
/**
* Created by Gracecoder on 2017/11/24.
*/
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.a();
}
}
package tmp;
/**
* Created by Gracecoder on 2017/11/24.
*/
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.b();
}
}
package tmp;
/**
* Created by Gracecoder on 2017/11/24.
*/
public class Run {
public static void main(String[] args){
Service service = new Service();
ThreadA a = new ThreadA(service);
ThreadB b = new ThreadB(service);
a.setName("A");
a.start();
b.setName("B");
b.start();
}
}
運行結果:
a begin
b begin
b end
a end
運行這段代碼首先線程A先獲取成員變量anyString的內置鎖,然後線程B訪問同步方法,獲取當前對象service的鎖,這裏anyString是對象service的成員變量,結果顯示它們異步調用了。
按照我一開始的想法,如果反一下,由線程B先訪問同步方法,獲取當前對象service的鎖之後,其他線程是否能獲取其對象成員的鎖? 我覺得 不能!但是通過運行代碼驗證,發現就算B先獲得service的鎖,A依舊能獲得其成員anyString的鎖,它們仍然是異步調用!
就很疑惑,我們獲取一個對象的鎖,但是不代表我們擁有其成員域對象的鎖!
小結論:
每個對象都可以用作一個實現同步的鎖,這些鎖被稱爲內置鎖!
對象的內置鎖和對象的狀態之間沒有內在的關聯,雖然大多數類都講內置鎖用作一種有效的加鎖機制,但對象的域並不一定通過內置鎖來保護。當獲取到與對象關聯的內置鎖時,並不能阻止其他線程訪問該對象,只能阻止其他線程獲取同一個鎖!
那顯然每個對象都有自己的內置鎖,儘管anyString是service的成員域,但它們兩各自的內置鎖也是不同的。所以就算反過來,B先獲取this(synchronized方法相當於同步塊synchronized(this))對象的鎖,A還是可以獲取anyString的鎖。除非我們在B的同步方法中,再次請求獲取anyString對象的鎖,那就會同步,阻塞到A完成了。
這裏還有一個類鎖的概念,在synchronized同步方法前面加個static,那麼訪問該方法,就要獲取當前類的鎖。
//同步的類方法
synchronized public static void printA()
{
//todo
}
注意:同樣的,類鎖和對象鎖也不存在關聯,它們是不同的鎖。類鎖並不包括該類具體對象的鎖,如同對象鎖並不包括其對象域的鎖。在具體代碼中,如果線程A先調用同步類方法(獲取了類鎖),線程B這時候調用同步方法,調用同步方法的前提是獲得當前對象鎖,而此時當前對象鎖空閒,所以可以異步執行。如果還有一個線程C要調用另一個同步類方法或者同步方法,則必須等A或B執行完畢後,才能獲得鎖,同步執行!
而類鎖對類的所有對象實例起作用的本質是:
實例對象如果要調用synchronized類方法,那麼就要獲取類鎖,此時不同對象獲取的類鎖都是同一個,那麼就會產生同步!
總結:
synchronized 同步的本質就是:是否使用同一個對象監視器
任何對象都有一個自己的內置鎖,同步或者異步,主要就是看多線程競爭的是否是同一把鎖。區分這一點纔是關鍵,當獲取到與對象關聯的內置鎖時,並不能阻止其他線程訪問該對象,只能阻止其他線程獲取同一個鎖!
synchronized(非this對象x)格式的寫法是將x對象本身作爲“對象監視器”,這樣就可以得出以下3個結論:
- 當多個線程同時執行synchronized(x){}同步代碼塊時呈同步效果(競爭this當前對象的鎖)
- 當其他線程執行x對象中synchronized同步方法時呈同步效果(競爭x對象)
- 當其他線程執行x對象方法裏面的synchronized(this)代碼塊時也是同步
需要注意的是:如果其他線程調用不加synchronized關鍵字的方法時,還是異步調用,因爲其他線程沒有去競爭鎖。
線程安全包括可見性和原子性,synchronized實現了原子性,同時間接現實可見性。而volatile只有可見性,沒有原子性。