synchronized 前言 相信大家都聽說過線程安全問題,在學習操作系統的時候有一個知識點是臨界資源,簡單的說就是一次只能讓一個進程操作的資源,但是我們在使用多線程的時候是併發操作的,並不能控制同時只對一個資源的訪問和修改,想要控制那麼有幾種操作,今天我們就來講講第一種方法:線程同步塊或者線程同 ...
synchronized
前言
相信大家都聽說過線程安全問題,在學習操作系統的時候有一個知識點是臨界資源,簡單的說就是一次只能讓一個進程操作的資源,但是我們在使用多線程的時候是併發操作的,並不能控制同時只對一個資源的訪問和修改,想要控制那麼有幾種操作,今天我們就來講講第一種方法:線程同步塊或者線程同步方法(synchronized)
實例
- 下麵舉一個例子說明
synchronized
關鍵字的使用
線程同步方法
public class Sychor {
public void insert(Thread thread) {
for (int i = 0; i < 10; i++) {
System.out.println(thread.getName() + "輸出: " + i);
}
}
public static void main(String[] args) {
final Sychor sychor = new Sychor();
Thread t1 = new Thread() {
public void run() {
sychor.insert(Thread.currentThread());
};
};
Thread t2 = new Thread() {
public void run() {
sychor.insert(Thread.currentThread());
};
};
t1.start();
t2.start();
}
}
其中輸出結果為下圖
從上面的結果可以看出這裡的兩個線程是同時執行
insert()
方法的,下麵我們在原有的代碼上添加synchronized
關鍵字看看效果如何,代碼如下:
public class Sychor {
public synchronized void insert(Thread thread) {
for (int i = 0; i < 10; i++) {
System.out.println(thread.getName() + "輸出: " + i);
}
}
public static void main(String[] args) {
final Sychor sychor = new Sychor();
Thread t1 = new Thread() {
public void run() {
sychor.insert(Thread.currentThread());
};
};
Thread t2 = new Thread() {
public void run() {
sychor.insert(Thread.currentThread());
};
};
t1.start();
t2.start();
}
}
上面程式的運行結果我就不列出來,自己可以試試,總之就是加上了
synchronized
關鍵字使得線程是一個一個的執行的,只有先執行完一個線程才能執行了另外一個線程。
線程同步塊
當然上面的我們使用的是線程同步方法,我們可以使用線程同步塊,這兩個相比線程同步塊更加靈活,只需要將需要同步的代碼放在同步塊中即可,代碼如下;
public class Sychor {
public void insert(Thread thread) {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println(thread.getName() + "輸出: " + i);
}
}
}
public static void main(String[] args) {
final Sychor sychor = new Sychor();
Thread t1 = new Thread() {
public void run() {
sychor.insert(Thread.currentThread());
};
};
Thread t2 = new Thread() {
public void run() {
sychor.insert(Thread.currentThread());
};
};
t1.start();
t2.start();
}
}
從上面的代碼中可以看出這種方式更加靈活,只需要將需要同步的代碼方法在同步塊中,不需要同步的代碼放在外面
詳細原因
- 我們知道每一個對象都有一把鎖,當我們使用線程同步方法或者線程同步塊的時候實際上獲得是對象的唯一的一把鎖,當一個線程獲得了這唯一的鎖,那麼其他的線程只能拒之門外了,註意這裡我們說是一個對象,也就是說是同一個對象,如果是不同的對象,那麼就不起作用了,因為不同對象有不同的對象鎖,比如我們將上面的程式改成如下:
public class Sychor {
public void insert(Thread thread) {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println(thread.getName() + "輸出: " + i);
}
}
}
public static void main(String[] args) {
//第一個線程
Thread t1 = new Thread() {
public void run() {
Sychor sychor = new Sychor(); //在run() 方法中創建一個對象
sychor.insert(Thread.currentThread());
};
};
//第二個線程
Thread t2 = new Thread() {
public void run() {
Sychor sychor = new Sychor(); //創建另外的一個對象
sychor.insert(Thread.currentThread());
};
};
t1.start();
t2.start();
}
}
從上面的結果可知,此時線程同步塊根本不起作用,因為他們調用的是不同對象的insert方法,獲得鎖是不一樣的
- 上面我們已經說過一個對象有一把鎖,線程同步方法和線程同步塊實際獲得的是對象的鎖,因此線程同步塊的括弧中填入的是
this
,我們都知道this
在一個類中的含義
- 一個類也有唯一的一把鎖,我們前面說的是使用對象調用成員方法,現在如果我們要調用類中的靜態方法,那麼我們可以使用線程同步方法或者同步塊獲得類中的唯一一把鎖,那麼對於多個線程同時調用同一個類中的靜態方法就可以實現控制了,代碼如下:
public class Sychor {
// 靜態方法
public static synchronized void insert(Thread thread)
{
for(int i=0;i<10;i++)
{
System.out.println(thread.getName()+"輸出 "+i);
}
}
public static void main(String[] args) {
//第一個線程
Thread t1 = new Thread() {
public void run() {
Sychor.insert(Thread.currentThread()); //直接使用類調用靜態方法
};
};
//第二個線程
Thread t2 = new Thread() {
public void run() {
Sychor.insert(Thread.currentThread()); //直接使用類調用靜態方法
};
};
t1.start();
t2.start();
}
}
註意
- 要想實現線程安全和同步控制,如果執行的是非
static
同步方法或者其中的同步塊,那麼一定要使用同一個對象,如果調用的是static同步方法或者其中的同步塊那麼一定要使用同一個類去調用
- 如果一個線程訪問的是
static
同步方法,而另外一個線程訪問的是非static的同步方法,此時這兩個是不會發生衝突的,因為一個是類的鎖,一個是對象的鎖
- 如果使用線程同步塊,那麼同步塊中的代碼是控制訪問的,但是外面的代碼是所有線程都可以訪問的
- 當一個正在執行同步代碼塊的線程出現了異常,那麼
jvm
會自動釋放當前線程所占用的鎖,因此不會出現由於異常導致死鎖的現象