Java中可以使用關鍵字synchronized進行線程同步控制,實作關鍵資源順序通路,避免由于多線程并發執行導緻的資料不一緻性等問題。synchronized的原理是對象螢幕(鎖),隻有擷取到螢幕的線程才能繼續執行,否則線程會等待擷取螢幕。Java中每個對象或者類都有一把鎖與之相關聯,對于對象來說,監視的是這個對象的執行個體變量,對于類來說,監視的是類變量(一個類本身是類Class的對象,是以與類關聯的鎖也是對象鎖)。synchronized關鍵字使用方式有兩種:synchronized方法和synchronized塊。這兩種監視區域都和一個引入對象相關聯,當到達這個監視區域時,JVM就會鎖住這個引用對象,當離開時會釋放這個引用對象上的鎖(有異常退出時,JVM會釋放鎖)。對象鎖是JVM内部機制,隻需要編寫同步方法或者同步塊即可,操作監視區域時JVM會自動擷取鎖或者釋放鎖。
首先來看同步方法的例子:
public class SynchronizedTest1 extends Thread
{
private synchronized void testSynchronizedMethod()
{
for (int i = 0; i < 10; i++)
{
System.out.println(Thread.currentThread().getName()
+ " testSynchronizedMethod:" + i);
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
@Override
public void run()
{
testSynchronizedMethod();
}
public static void main(String[] args)
{
SynchronizedTest1 t = new SynchronizedTest1();
t.start();
t.testSynchronizedMethod();
}
}
運作該程式輸出結果為:
main testSynchronizedMethod:0
main testSynchronizedMethod:1
main testSynchronizedMethod:2
main testSynchronizedMethod:3
main testSynchronizedMethod:4
main testSynchronizedMethod:5
main testSynchronizedMethod:6
main testSynchronizedMethod:7
main testSynchronizedMethod:8
main testSynchronizedMethod:9
Thread-0 testSynchronizedMethod:0
Thread-0 testSynchronizedMethod:1
Thread-0 testSynchronizedMethod:2
Thread-0 testSynchronizedMethod:3
Thread-0 testSynchronizedMethod:4
Thread-0 testSynchronizedMethod:5
Thread-0 testSynchronizedMethod:6
Thread-0 testSynchronizedMethod:7
Thread-0 testSynchronizedMethod:8
Thread-0 testSynchronizedMethod:9
可以看到testSynchronizedMethod方法在兩個線程之間同步執行。
如果此時将main方法修改為如下所示,則兩個線程并不能同步執行,因為此時兩個線程的同步螢幕不是同一個對象,不能起到同步的作用。
public static void main(String[] args)
{
Thread t = new SynchronizedTest1();
t.start();
Thread t1 = new SynchronizedTest1();
t1.start();
}
此時輸出結果如下所示:
Thread-0 testSynchronizedMethod:0
Thread-1 testSynchronizedMethod:0
Thread-0 testSynchronizedMethod:1
Thread-1 testSynchronizedMethod:1
Thread-0 testSynchronizedMethod:2
Thread-1 testSynchronizedMethod:2
Thread-0 testSynchronizedMethod:3
Thread-1 testSynchronizedMethod:3
Thread-0 testSynchronizedMethod:4
Thread-1 testSynchronizedMethod:4
Thread-0 testSynchronizedMethod:5
Thread-1 testSynchronizedMethod:5
Thread-0 testSynchronizedMethod:6
Thread-1 testSynchronizedMethod:6
Thread-0 testSynchronizedMethod:7
Thread-1 testSynchronizedMethod:7
Thread-0 testSynchronizedMethod:8
Thread-1 testSynchronizedMethod:8
Thread-0 testSynchronizedMethod:9
Thread-1 testSynchronizedMethod:9
若想修改後的main方法能夠在兩個線程之間同步運作,需要将testSynchronizedMethod方法聲明為靜态方法,這樣兩個線程的螢幕是同一個對象(類對象),能夠同步執行。修改後的代碼如下所示:
public class SynchronizedTest1 extends Thread
{
private static synchronized void testSynchronizedMethod()
{
for (int i = 0; i < 10; i++)
{
System.out.println(Thread.currentThread().getName()
+ " testSynchronizedMethod:" + i);
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
@Override
public void run()
{
testSynchronizedMethod();
}
public static void main(String[] args)
{
Thread t = new SynchronizedTest1();
t.start();
Thread t1 = new SynchronizedTest1();
t1.start();
}
}
輸出結果如下:
Thread-0 testSynchronizedMethod:0
Thread-0 testSynchronizedMethod:1
Thread-0 testSynchronizedMethod:2
Thread-0 testSynchronizedMethod:3
Thread-0 testSynchronizedMethod:4
Thread-0 testSynchronizedMethod:5
Thread-0 testSynchronizedMethod:6
Thread-0 testSynchronizedMethod:7
Thread-0 testSynchronizedMethod:8
Thread-0 testSynchronizedMethod:9
Thread-1 testSynchronizedMethod:0
Thread-1 testSynchronizedMethod:1
Thread-1 testSynchronizedMethod:2
Thread-1 testSynchronizedMethod:3
Thread-1 testSynchronizedMethod:4
Thread-1 testSynchronizedMethod:5
Thread-1 testSynchronizedMethod:6
Thread-1 testSynchronizedMethod:7
Thread-1 testSynchronizedMethod:8
Thread-1 testSynchronizedMethod:9
同步塊的情況與同步方法類似,隻是同步塊将同步控制的粒度縮小,這樣能夠更好的發揮多線程并行執行的效率。
使用this對象控制同一對象執行個體之間的同步:
public class SynchronizedTest2 extends Thread
{
private void testSynchronizedBlock()
{
synchronized (this)
{
for (int i = 0; i < 10; i++)
{
System.out.println(Thread.currentThread().getName()
+ " testSynchronizedBlock:" + i);
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
@Override
public void run()
{
testSynchronizedBlock();
}
public static void main(String[] args)
{
SynchronizedTest2 t = new SynchronizedTest2();
t.start();
t.testSynchronizedBlock();
}
}
輸出結果:
main testSynchronizedBlock:0
main testSynchronizedBlock:1
main testSynchronizedBlock:2
main testSynchronizedBlock:3
main testSynchronizedBlock:4
main testSynchronizedBlock:5
main testSynchronizedBlock:6
main testSynchronizedBlock:7
main testSynchronizedBlock:8
main testSynchronizedBlock:9
Thread-0 testSynchronizedBlock:0
Thread-0 testSynchronizedBlock:1
Thread-0 testSynchronizedBlock:2
Thread-0 testSynchronizedBlock:3
Thread-0 testSynchronizedBlock:4
Thread-0 testSynchronizedBlock:5
Thread-0 testSynchronizedBlock:6
Thread-0 testSynchronizedBlock:7
Thread-0 testSynchronizedBlock:8
Thread-0 testSynchronizedBlock:9
使用class對象控制不同執行個體之間的同步:
public class SynchronizedTest2 extends Thread
{
private void testSynchronizedBlock()
{
synchronized (SynchronizedTest2.class)
{
for (int i = 0; i < 10; i++)
{
System.out.println(Thread.currentThread().getName()
+ " testSynchronizedBlock:" + i);
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
@Override
public void run()
{
testSynchronizedBlock();
}
public static void main(String[] args)
{
Thread t = new SynchronizedTest2();
t.start();
Thread t2 = new SynchronizedTest2();
t2.start();
}
}
輸出結果:
Thread-0 testSynchronizedBlock:0
Thread-0 testSynchronizedBlock:1
Thread-0 testSynchronizedBlock:2
Thread-0 testSynchronizedBlock:3
Thread-0 testSynchronizedBlock:4
Thread-0 testSynchronizedBlock:5
Thread-0 testSynchronizedBlock:6
Thread-0 testSynchronizedBlock:7
Thread-0 testSynchronizedBlock:8
Thread-0 testSynchronizedBlock:9
Thread-1 testSynchronizedBlock:0
Thread-1 testSynchronizedBlock:1
Thread-1 testSynchronizedBlock:2
Thread-1 testSynchronizedBlock:3
Thread-1 testSynchronizedBlock:4
Thread-1 testSynchronizedBlock:5
Thread-1 testSynchronizedBlock:6
Thread-1 testSynchronizedBlock:7
Thread-1 testSynchronizedBlock:8
Thread-1 testSynchronizedBlock:9
使用synchronized關鍵字進行同步控制時,一定要把握好對象螢幕,隻有獲得螢幕的程序可以運作,其它都需要等待擷取螢幕。任何一個非null的對象都可以作為對象螢幕,當synchronized作用在方法上時,鎖住的便是對象執行個體(this);當作用在靜态方法時鎖住的便是對象對應的Class執行個體。
總結:
synchronized是通過軟體(JVM)實作的,簡單易用,即使在JDK5之後有了Lock,仍然被廣泛地使用。
synchronized實際上是非公平的,新來的線程有可能立即獲得螢幕,而在等待區中等候已久的線程可能再次等待,不過這種搶占的方式可以預防饑餓。
synchronized隻有鎖隻與一個條件(是否擷取鎖)相關聯,不靈活,後來Condition與Lock的結合解決了這個問題。
多線程競争一個鎖時,其餘未得到鎖的線程隻能不停的嘗試獲得鎖,而不能中斷。高并發的情況下會導緻性能下降。ReentrantLock的lockInterruptibly()方法可以優先考慮響應中斷。 一個線程等待時間過長,它可以中斷自己,然後ReentrantLock響應這個中斷,不再讓這個線程繼續等待。有了這個機制,使用ReentrantLock時就不會像synchronized那樣産生死鎖了。
參考資料:
JAVA并發程式設計學習筆記之synchronized
深入JVM鎖機制1-synchronized
java 對象鎖