Android多執行緒-----併發和同步(synchronized)
一、鎖
物件的內建鎖和物件的狀態之間是沒有內在的關聯的,雖然大多數類都將內建鎖用做一種有效的加鎖機制,但物件的域並不一定通過內建鎖來保護。當獲取到與物件關聯的內建鎖時,並不能阻止其他執行緒訪問該物件,當某個執行緒獲得物件的鎖之後,只能阻止其他執行緒獲得同一個鎖。之所以每個物件都有一個內建鎖,是為了免去顯式地建立鎖物件。
所以synchronized只是一個內建鎖的加鎖機制,當某個方法加上synchronized關鍵字後,就表明要獲得該內建鎖才能執行,並不能阻止其他執行緒訪問不需要獲得該內建鎖的方法。
java內建鎖是一個互斥鎖,這就是意味著最多隻有一個執行緒能夠獲得該鎖,當執行緒A嘗試去獲得執行緒B持有的內建鎖時,執行緒A必須等待或者阻塞,知道執行緒B釋放這個鎖,如果B執行緒不釋放這個鎖,那麼A執行緒將永遠等待下去,這對高併發的系統是致命的
java的物件鎖和類鎖:java的物件鎖和類鎖在鎖的概念上基本上和內建鎖是一致的,但是,兩個鎖實際是有很大的區別的,物件鎖是用於物件例項方法,或者一個物件例項上的,類鎖是用於類的靜態方法或者一個類的class物件上的。我們知道,類的物件例項可以有很多個,但是每個類只有一個class物件,所以不同物件例項的物件鎖是互不干擾的,但是每個類只有一個類鎖。
Java中每一個物件都可以作為鎖,這是synchronized實現同步的基礎:
1. 普通同步方法,鎖是當前例項物件
2. 靜態同步方法,鎖是當前類的class物件
3. 同步方法塊,鎖是括號裡面的物件
下面講解
synchronized修飾方法,
synchronized修飾靜態方法,
synchronized(this),
synchronized(類),
synchronized(變數)之間的區別
二、修飾方法和程式碼塊
修飾方法是在方法的前面加synchronized,synchronized修飾方法和修飾一個程式碼塊類似,只是作用範圍不一樣,修飾程式碼塊是大括號括起來的範圍,而修飾方法範圍是整個函式。
public synchronized void method() { // todo } public void method() { synchronized(this) { // todo } }
雖然可以使用synchronized來定義方法,但synchronized並不屬於方法定義的一部分,因此,synchronized關鍵字不能被繼承。如果在父類中的某個方法使用了synchronized關鍵字,而在子類中覆蓋了這個方法,在子類中的這個方法預設情況下並不是同步的,而必須顯式地在子類的這個方法中加上synchronized關鍵字才可以。當然,還可以在子類方法中呼叫父類中相應的方法,這樣雖然子類中的方法不是同步的,但子類呼叫了父類的同步方法,因此,子類的方法也就相當於同步了。這兩種方式的例子程式碼如下:
在子類方法中加上synchronized關鍵字
class Parent {
public synchronized void method() { }
}
class Child extends Parent {
public synchronized void method() { }
}
注意:
- 在定義介面方法時不能使用synchronized關鍵字。
- 構造方法不能使用synchronized關鍵字,但可以使用synchronized程式碼塊來進行同步。
(一)物件鎖的synchronized修飾方法和程式碼塊
public class TestSynchronized {
public void test1() {
synchronized (this) {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public synchronized void test2() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final TestSynchronized myt2 = new TestSynchronized();
Thread test1 = new Thread(new Runnable() {
public void run() {
myt2.test1();
}
}, "test1");
Thread test2 = new Thread(new Runnable() {
public void run() {
myt2.test2();
}
}, "test2");
test1.start();
test2.start();
}
}
test2 : 4
test2 : 3
test2 : 2
test2 : 1
test2 : 0
test1 : 4
test1 : 3
test1 : 2
test1 : 1
test1 : 0
上述的程式碼,第一個方法時用了同步程式碼塊的方式進行同步,傳入的物件例項是this,表明是當前物件,當然,如果需要同步其他物件例項,也可傳入其他物件的例項;第二個方法是修飾方法的方式進行同步。因為第一個同步程式碼塊傳入的this,所以兩個同步程式碼所需要獲得的物件鎖都是同一個物件鎖 ; main方法時分別開啟兩個執行緒,分別呼叫test1和test2方法,那麼兩個執行緒都需要獲得該物件鎖,另一個執行緒必須等待。上面也給出了執行的結果可以看到:直到test2執行緒執行完畢,釋放掉鎖,test1執行緒才開始執行。
如果我們把test2方法的synchronized關鍵字去掉,執行結果會如何呢?
test1 : 4
test2 : 4
test2 : 3
test1 : 3
test1 : 2
test2 : 2
test2 : 1
test1 : 1
test2 : 0
test1 : 0
上面是執行結果,我們可以看到,結果輸出是交替著進行輸出的,這是因為,某個執行緒得到了物件鎖,但是另一個執行緒還是可以訪問沒有進行同步的方法或者程式碼。進行了同步的方法(加鎖方法)和沒有進行同步的方法(普通方法)是互不影響的,一個執行緒進入了同步方法,得到了物件鎖,其他執行緒還是可以訪問那些沒有同步的方法(普通方法)
(二)類鎖的修飾(靜態)方法和程式碼塊
public class TestSynchronized {
public synchronized void test1() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static synchronized void test2() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final TestSynchronized myt2 = new TestSynchronized();
Thread test1 = new Thread(new Runnable() {
public void run() {
myt2.test1();
}
}, "test1");
Thread test2 = new Thread(new Runnable() {
public void run() {
TestSynchronized.test2();
}
}, "test2");
test1.start();
test2.start();
}
}
test1 :4
test2 :4
test1 :3
test2 :3
test2 :2
test1 :2
test2 :1
test1 :1
test1 :0
test2 :0
上面程式碼synchronized同時修飾靜態方法和例項方法,但是執行結果是交替進行的,這證明了類鎖和物件鎖是兩個不一樣的鎖,控制著不同的區域,它們是互不干擾的。同樣,執行緒獲得物件鎖的同時,也可以獲得該類鎖,即同時獲得兩個鎖,這是允許的。
三、對比
public class ThreadTest {
public static final String Lock = "lock";
public final Object obj = new Object();
synchronized void test1() throws InterruptedException {
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("test1=" + i);
}
}
synchronized static void test2() throws InterruptedException {
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("test2=" + i);
}
}
void test3() throws InterruptedException {
synchronized (this) {
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("test3=" + i);
}
}
}
void test4() throws InterruptedException {
synchronized (this.getClass()) {
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("test4=" + i);
}
}
}
void test5() throws InterruptedException {
synchronized (Lock) {
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("test5=" + i);
}
}
}
void test6() throws InterruptedException {
synchronized (obj) {
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("test6=" + i);
}
}
}
void test7() throws InterruptedException {
synchronized (ThreadTest.Lock) {
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("test7=" + i);
}
}
}
}
一、同一個物件的同一個同步方法是同步的
二、同一個物件的synchronized方法和synchronized(this)塊是同步的
方法test1和test3同步
這兩個方法的鎖都是加在當前物件上
三、類的static方法和synchronized(該類)塊是同步的
方法test2和test4同步
這兩個方法的鎖都是加在ThreadTest類上面,方法test4的synchronized (this.getClass())塊換成synchronized (ThreadTest.class),結果是一樣的,但是如果換成比如synchronized (String.class)就不能同步了
因為鎖是加在類上面,所以不僅相同類的這兩種方法是同步的,同一個類的不同物件之間的這兩種方法也是同步的
四、第二點的兩種方法和第三點的兩種方法不同步
test1和test2不同步,test1和test4不同步
test3和test2不同步,test3和test4不同步
五、synchronized(變數)跟前面四種都不同步
因為鎖加的目標不一樣,既不是當前類也不是當前物件。
synchronized (obj)和synchronized (this)的用法基本一致,對當前物件加鎖。
而synchronized (Lock)除了當前物件以外,本類的其他新建物件也會同步加鎖,甚至於其他類如果呼叫的synchronized (ThreadTest.Lock)也會同步。
六、ThreadTest2的test7方法跟ThreadTest的test5方法是同步的;