Java 提供了兩種鎖機制來控制多個線程對共享資源的互斥訪問,第一個是 JVM 實現的 synchronized,而另一個是 JDK 實現的 ReentrantLock。
synchronized
synchronized關鍵字最主要幾種使用方式:
(1)同步一個代碼塊:
只作用於同一個對象,如果調用兩個對象上的同步代碼塊,就不會進行同步。
public void func() {
synchronized (this) {
// ...
}
}
對於以下代碼, 執行了兩個線程,由於調用的是同一個對象的同步代碼塊,因此這兩個線程會進行同步,當一個線程進入同步語句塊時,另一個線程就必須等待。
public class SynchronizedExample implements Runnable {
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
public static void main(String[] args){
SynchronizedExample e1 = new SynchronizedExample();
Thread t1 = new Thread(e1);
Thread t2 = new Thread(e1);
t1.start();
t2.start();
}
}
對於以下代碼,兩個線程調用了不同對象的同步代碼塊,因此這兩個線程就不需要同步。從輸出結果可以看出,兩個線程交叉執行(運行結果不一定與截圖完全一樣)。
public class SynchronizedExample implements Runnable {
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
public static void main(String[] args){
SynchronizedExample e1 = new SynchronizedExample();
SynchronizedExample e2 = new SynchronizedExample();
Thread t1 = new Thread(e1);
Thread t2 = new Thread(e2);
t1.start();
t2.start();
}
}
(2)同步一個方法:
它和同步代碼塊一樣,作用於同一個對象。
public synchronized void func () {
// ...
}
(3)同步一個類:
作用於整個類,也就是說兩個線程調用同一個類的不同對象上的這種同步語句,也會進行同步
public void func() {
synchronized (SynchronizedExample.class) {
// ...
}
}
public class SynchronizedExample implements Runnable {
@Override
public void run() {
synchronized (SynchronizedExample.class) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
public static void main(String[] args){
SynchronizedExample e1 = new SynchronizedExample();
SynchronizedExample e2 = new SynchronizedExample();
Thread t1 = new Thread(e1);
Thread t2 = new Thread(e2);
t1.start();
t2.start();
}
}
(4)同步一個靜態方法
相當於給當前類對象加鎖,作用於整個類
public synchronized static void fun() {
// ...
}
ReenTrantLock
在java多線程中,可以使用synchronized來實現線程之間同步互斥,但在jdk1.5中新增加了ReenTrantLock類也能達到同樣效果,並且在擴展功能上也更加強大。
public class MyService {
private Lock lock = new ReentrantLock();
public void testMethod(){
lock.lock();
for(int i=1; i<4; i++){
System.out.println("ThreadName="+Thread.currentThread().getName()+" "+i);
}
lock.unlock();
}
}
public class TestReentrantLock extends Thread{
private MyService service;
public TestReentrantLock(MyService service) {
this.service = service;
}
@Override
public void run(){
service.testMethod();
}
public static void main(String[] args){
MyService service = new MyService();
TestReentrantLock thread1 = new TestReentrantLock(service);
TestReentrantLock thread2 = new TestReentrantLock(service);
TestReentrantLock thread3 = new TestReentrantLock(service);
thread1.start();
thread2.start();
thread3.start();
}
}
當前線程依次打印1,2,3,線程之間打印的順序是隨機的
新版本 Java 對 synchronized 進行了很多優化,例如自旋鎖等,所以synchronized 與 ReentrantLock 大致相同。這裏介紹一下ReenTrantLock的一些高級功能,也就是synchronized 不能實現的功能
(1)等待可中斷
當持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待,改爲處理其他事情。
ReentrantLock 可中斷,而 synchronized 不行。
(2)公平鎖
公平鎖是指多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖。所謂的公平鎖就是先等待的線程先獲得鎖。ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。 ReenTrantLock默認情況是非公平的,可以通過 ReenTrantLock類的ReentrantLock(boolean fair)構造方法來制定是否是公平的。
(3)鎖綁定多個條件
synchronized關鍵字與wait()和notify/notifyAll()方法相結合可以實現等待/通知機制,ReentrantLock類當然也可以實現,但是需要藉助於Condition接口與newCondition() 方法。
看這個例子:使用Condition實現等待/通知
public class MyService {
private Lock lock = new ReentrantLock();
public Condition condition = lock.newCondition();
public void await(){
try{
lock.lock();
System.out.println("await時間爲"+System.currentTimeMillis());
condition.await();
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void signal(){
try{
lock.lock();
System.out.println("signal時間爲"+System.currentTimeMillis());
condition.signal();
}finally{
lock.unlock();
}
}
}
public class TestCondition extends Thread{
private MyService service;
public TestCondition(MyService service){
this.service = service;
}
@Override
public void run(){
service.await();
}
public static void main(String[] args) throws InterruptedException{
MyService service = new MyService();
TestCondition thread = new TestCondition(service);
thread.start();
Thread.sleep(3000);
service.signal();
}
}
Object類中的wait()方法相當於Condition類中的await()方法
wait(long timeout)方法相當於await(long time,TimeUnit unit)
notify()方法相當於signal()方法
notifyAll()方法相當於signalAll()方法
Condition是JDK1.5之後纔有的,它具有很好的靈活性,比如可以實現多路通知功能也就是在一個Lock對象中可以創建多個Condition實例(即對象監視器),線程對象可以註冊在指定的Condition中,從而可以有選擇性的進行線程通知,在調度線程上更加靈活。 在使用notify/notifyAll()方法進行通知時,被通知的線程是由 JVM 選擇的,用ReentrantLock類結合Condition實例可以實現“選擇性通知” ,這個功能非常重要,而且是Condition接口默認提供的。
而synchronized關鍵字就相當於整個Lock對象中只有一個Condition實例,所有的線程都註冊在它一個身上。如果執行notifyAll()方法的話就會通知所有處於等待狀態的線程這樣會造成很大的效率問題,而Condition實例的signalAll()方法 只會喚醒註冊在該Condition實例中的所有等待線程。
注意
除非需要使用 ReentrantLock 的高級功能,否則優先使用 synchronized。這是因爲 synchronized 是 JVM 實現的一種鎖機制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。並且使用 synchronized 不用擔心沒有釋放鎖而導致死鎖問題,因爲 JVM 會確保鎖的釋放。
synchronized與ReenTrantLock的異同
(1)synchronized 是 JVM 實現的,而 ReentrantLock 是 JDK 實現的。
(2)ReentrantLock 可中斷,而 synchronized 不行。
(3)synchronized 中的鎖是非公平的,ReentrantLock 默認情況下也是非公平的,但是也可以是公平的。
(4)一個 ReentrantLock 可以同時綁定多個 Condition 對象。synchronized 則不行
(5)synchronized 和ReentrantLock 都是可重入鎖