在JAVA中,執行緒與執行緒之間的資料是共享的,因此,當多個執行緒同時改變相同的物件,執行緒會相互傾軋。根據執行緒訪問資料的不同線性,會產生被腐蝕的物件。
為了避免這種現象,我們需要對需要保護的物件上鎖,這樣在同一時間只能有一個執行緒訪問這個資料,而其他的執行緒進入等待佇列,知道這個執行緒訪問完資料並釋放鎖,等待佇列中的執行緒才能獲得鎖。
上鎖的方法有多種,這裡只寫出常用的三種
1.synchronized程式碼塊
synchronized (object)
{
}
例如:
public class TraditionalThreadSynchronized {
public static void main(String[] args) {
final Outputter output = new Outputter();
new Thread() {
public void run() {
output.output("zhangsan");
};
}.start();
new Thread() {
public void run() {
output.output("lisi");
};
}.start();
}
}
class Outputter {
public void output(String name) {
// TODO 為了保證對name的輸出不是一個原子操作,這裡逐個輸出name的每個字元
synchronized (this)
{
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
// Thread.sleep(10);
}
}
}
}
object 必須是需要互斥的多個執行緒間的共享物件。
因此下面這段程式碼的鎖是無效的
public class TraditionalThreadSynchronized {
public static void main(String[] args) {
final Outputter output = new Outputter();
new Thread() {
public void run() {
output.output("zhangsan");
};
}.start();
new Thread() {
public void run() {
output.output("lisi");
};
}.start();
}
}
class Outputter {
public void output(String name) {
// TODO 為了保證對name的輸出不是一個原子操作,這裡逐個輸出name的每個字元
Object object=new Object();
synchronized (object)
{
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
// Thread.sleep(10);
}
}
}
}
2.synchronized 方法
public synchronized void output(String name) {
// TODO 執行緒輸出方法
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
}
這種方式就相當於用this鎖住整個方法內的程式碼塊,如果用synchronized加在靜態方法上,就相當於用××××.class鎖住整個方法內的程式碼塊。使用synchronized在某些情況下會造成死鎖,死鎖問題以後會說明。使用synchronized修飾的方法或者程式碼塊可以看成是一個原子操作。
每個鎖對都有兩個佇列,一個是就緒佇列,一個是阻塞佇列,就緒佇列儲存了將要獲得鎖的執行緒,阻塞佇列儲存了被阻塞的線 程,當一個執行緒被喚醒(notify)後,才會進入到就緒佇列,等待CPU的排程,反之,當一個執行緒被wait後,就會進入阻塞佇列,等待下一次被喚醒, 這個涉及到執行緒間的通訊,一個執行緒執行互斥程式碼過程如下:
1. 獲得同步鎖;
2. 清空工作記憶體;
3. 從主記憶體拷貝物件副本到工作記憶體;
4. 執行程式碼(計算或者輸出等);
5. 重新整理主記憶體資料;
6. 釋放同步鎖。
所以,synchronized既保證了多執行緒的併發有序性,又保證了多執行緒的記憶體可見性。
3.lock方法
Lock lc=new ReentrantLock();
lc.lock();
// do work
lc.unlock();
這種方法一般不推薦使用