008.多執行緒-synchronized
為了解決執行緒安全問題,
我們的做法是:不要讓多個執行緒同時對一個全域性變數作寫的操作。
常用的做法就是加鎖,來實現執行緒的同步。
自動鎖synchronized和手動鎖lock。
由於synchronized不需要手動釋放鎖,丟擲異常也可自動釋放鎖,
故而常用synchronized自動鎖。
一個執行緒拿到鎖後,其他執行緒則只能排隊,等待鎖的釋放。
程式碼執行完畢或者程式丟擲異常,鎖均會被釋放。
synchronized 程式碼塊
/**
* 相當於同步函式
*/
public void test1_() {
synchronized (this ) {
for (int i = 1; i < 500; i++) {
System.out.println("test1_..." + i);
}
}
}
/**
* 相當於靜態同步函式
*/
public void test1s_() {
synchronized (Test.class) {
for (int i = 1; i < 500; i++) {
System.out. println("test1s_..." + i);
}
}
}
synchronized(lock-object){
}
括號後面要跟一個物件,
這個物件充當鎖的作用。
Java中,全部都是物件,不相同的常量字串也是不同的物件。
同步函式: (例項物件鎖)
在方法上修飾synchronized
public synchronized void test1() {
for (int i = 1; i < 500; i++) {
System.out.println("test1..." + i);
}
}
靜態同步函式: (類物件鎖)
方法上加上static關鍵字,使用synchronized 關鍵字修飾
或者使用 類.class 檔案。
public static synchronized void test1s() {
for (int i = 1; i < 500; i++) {
System.out.println("test1..." + i);
}
}
public void test1s_() {
synchronized (Test.class) {
for (int i = 1; i < 500; i++) {
System.out.println("test1s_..." + i);
}
}
}
若類物件被鎖,則類物件的所有同步方法全部被鎖。
若例項物件被鎖,則該例項物件的所有同步方法全部被鎖。
不是同一個物件,例項物件鎖沒有約束。
synchronized程式碼塊的優勢:
- 只對需要同步的程式碼進行同步
- 與wait() 、notify() 、notifyAll() 一起使用時,比較方便
package cn.qbz.thread;
public class WaitNotifyTest {
public static void main(String[] args) {
new Thread(new Produce()).start();
new Thread(new Consumer()).start();
}
}
class Produce implements Runnable {
public void run() {
int count = 5;
while (count > 0) {
synchronized ("lock-object") { //此處可以鎖定任何物件,只要鎖定Produce和Consumer中的物件一樣
System.out.println("A");
count--;
"lock-object".notify();// 喚醒因為呼叫物件的wait()而等待的執行緒
if (count > 0) {
try {
"lock-object".wait();//釋放本執行緒的物件鎖,釋放CPU
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Consumer implements Runnable {
public void run() {
int count = 5;
while (count > 0) {
synchronized ("lock-object") {
System.out.println("B");
count--;
"lock-object".notify(); // 喚醒因為呼叫物件的wait()而等待的執行緒
if (count > 0) {
try {
"lock-object".wait(); //釋放本執行緒的物件鎖,釋放CPU
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
wait():
釋放佔有的物件鎖,執行緒進入等待池,釋放cpu。
其他正在等待的執行緒即可搶佔此鎖,獲得鎖的執行緒即可執行程式。
sleep():
執行緒休眠,休眠期間,釋放cpu,但不釋放佔有的物件鎖。
即:休眠期間,其他執行緒依然無法進入此同步程式碼塊內。
notify():
喚醒因為呼叫物件的wait()而等待的執行緒。
呼叫notify()後,並不會立即釋放鎖,
而是直到synchronized程式碼塊中全部執行完畢,才釋放鎖。
JVM會在等待的執行緒中排程一個執行緒去獲得此鎖,執行程式碼。
notifyAll()
喚醒所有等待的執行緒。
notify與notifyAll
鎖池:
假設執行緒A已經擁有了某個物件鎖(非類鎖),
此時想獲取此物件鎖的其他執行緒,將進入此物件的鎖池中,
參與下次鎖的競爭。
等待池:
假設執行緒A呼叫了物件鎖的wait()方法,
執行緒A會釋放該物件鎖,並進入此物件的等待池中。
等待池中的執行緒不會參與物件鎖的競爭。
notify呼叫後,只會將等待池中的一個隨機執行緒移到鎖池中。
notifyAll呼叫後,會將全部執行緒移到鎖池中。
notify有一定機率造成死鎖。