java併發之----synchronized與ReenTrantLock
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 都是可重入鎖