Java鎖機制 synchronized 詳解
進行多線程編程的時候,需要考慮的是線程間的同步問題。對於共用的資源,需要進行互斥的訪問。在Java中可以使用一些手段來達到線程同步的目的:
1. synchronized
2. ThreadLocal,執行緒區域變數
3. Java.util.concurrent.Lock
Java中,線程會共用堆上的執行個體變數以及方法區的類變數,而棧上的資料是私人的,不必進行保護。synchronized方法或synchronized塊將標記一塊監視地區,線程在進入該地區時,需要獲得對象鎖或類鎖,JVM將自動上鎖。在這裡,我們將探討synchronized使用時的三種情況:
1. 在對象上使用synchronized
2. 在普通成員方法上使用synchronized
3. 在靜態成員方法上使用synchronized
這三種線程同步的表現有何不同?
下面通過三段範例程式碼來示範這三種情況。這裡類比線程報數的情境。
情況一:在普通成員函數上使用synchronized
public class MyThread extends Thread {
public static void main(String[] args) throws Exception {
for (int i = 1; i < 100; i++) {
MyThread t = new MyThread();
t.setName("Thread="+i);
t.start();
Thread.sleep(100);
}
}
@Override
public synchronized void run() {
for (int i = 1; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
對一個成員函數使用synchronized進行加鎖,所擷取的鎖,是方法所在對象本身的對象鎖。在這裡,每個線程都以自身的對象作為對象鎖,要對線程進行同步,要求鎖對象必須唯一,所以這裡多個線程間同步失敗。
情況二:在對象上使用synchronized
這裡在類中增加一個成員變數lock,在該變數上使用synchronized:
public class MyThread1 extends Thread {
private String lock;
public MyThread1(String lock) {
this.lock = lock;
}
public static void main(String[] args) throws Exception {
String lock = new String("lock");
for (int i = 1; i < 100; i++) {
Thread t = new MyThread1(lock);
t.setName("Thread=" + i);
t.start();
Thread.sleep(100);
}
}
@Override
public void run() {
synchronized (lock) {
for (int i = 1; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
100個線程在建立的時候,都傳遞了同一個lock對象(在main中建立的)去初始化線程類成員lock,因此,這100個線程都在同一個lock對象上進行synchronized同步。因此線程同步成功。
情況三:在靜態成員函數上使用synchronized
public class MyThread2 extends Thread {
public static void main(String[] args) throws Exception {
for (int i = 1; i < 10; i++) {
Thread t = new MyThread2();
t.setName("Thread=" + i);
t.start();
Thread.sleep(10);
}
}
public static synchronized void func() {
for (int i = 1; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
@Override
public void run() {
func();
}
}
這種情況下,線程獲得的鎖是對象鎖,而對象鎖是唯一的,因此多個進程間也能同步成功。