【轉(zhuǎn)】-synchronized與Lock的區(qū)別與使用
詳解synchronized與Lock的區(qū)別與使用
該博客轉(zhuǎn)載自?淳安郭富城?的?詳解synchronized與Lock的區(qū)別與使用
1. 引言:
昨天在學(xué)習(xí)別人分享的面試經(jīng)驗(yàn)時(shí),看到Lock的使用。想起自己在上次面試也遇到了synchronized與Lock的區(qū)別與使用。于是,我整理了兩者的區(qū)別和使用情況,同時(shí),對(duì)synchronized的使用過程一些常見問題的總結(jié),最后是參照源碼和說明文檔,對(duì)Lock的使用寫了幾個(gè)簡(jiǎn)單的Demo。請(qǐng)大家批評(píng)指正。
1.1 技術(shù)點(diǎn):
1.1.1 線程與進(jìn)程:
在開始之前先把進(jìn)程與線程進(jìn)行區(qū)分一下,一個(gè)程序最少需要一個(gè)進(jìn)程,而一個(gè)進(jìn)程最少需要一個(gè)線程。關(guān)系是線程–>進(jìn)程–>程序的大致組成結(jié)構(gòu)。所以線程是程序執(zhí)行流的最小單位,而進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。以下我們所有討論的都是建立在線程基礎(chǔ)之上。
1.1.2 Thread的幾個(gè)重要方法:
我們先了解一下Thread的幾個(gè)重要方法。a、start()方法,調(diào)用該方法開始執(zhí)行該線程;b、stop()方法,調(diào)用該方法強(qiáng)制結(jié)束該線程執(zhí)行;c、join方法,調(diào)用該方法等待該線程結(jié)束。d、sleep()方法,調(diào)用該方法該線程進(jìn)入等待。e、run()方法,調(diào)用該方法直接執(zhí)行線程的run()方法,但是線程調(diào)用start()方法時(shí)也會(huì)運(yùn)行run()方法,區(qū)別就是一個(gè)是由線程調(diào)度運(yùn)行run()方法,一個(gè)是直接調(diào)用了線程中的run()方法!!
看到這里,可能有些人就會(huì)問啦,那wait()和notify()呢?要注意,其實(shí)wait()與notify()方法是Object的方法,不是Thread的方法!!同時(shí),wait()與notify()會(huì)配合使用,分別表示線程掛起和線程恢復(fù)。
這里還有一個(gè)很常見的問題,順帶提一下:wait()與sleep()的區(qū)別,簡(jiǎn)單來說wait()會(huì)釋放對(duì)象鎖而sleep()不會(huì)釋放對(duì)象鎖。這些問題有很多的資料,不再贅述。
1.1.3 線程狀態(tài):
線程總共有5大狀態(tài),通過上面第二個(gè)知識(shí)點(diǎn)的介紹,理解起來就簡(jiǎn)單了。
新建狀態(tài):新建線程對(duì)象,并沒有調(diào)用start()方法之前
就緒狀態(tài):調(diào)用start()方法之后線程就進(jìn)入就緒狀態(tài),但是并不是說只要調(diào)用start()方法線程就馬上變?yōu)楫?dāng)前線程,在變?yōu)楫?dāng)前線程之前都是為就緒狀態(tài)。值得一提的是,線程在睡眠和掛起中恢復(fù)的時(shí)候也會(huì)進(jìn)入就緒狀態(tài)哦。
運(yùn)行狀態(tài):線程被設(shè)置為當(dāng)前線程,開始執(zhí)行run()方法。就是線程進(jìn)入運(yùn)行狀態(tài)
阻塞狀態(tài):線程被暫停,比如說調(diào)用sleep()方法后線程就進(jìn)入阻塞狀態(tài)
死亡狀態(tài):線程執(zhí)行結(jié)束
1.1.4 鎖類型
可重入鎖:在執(zhí)行對(duì)象中所有同步方法不用再次獲得鎖
可中斷鎖:在等待獲取鎖過程中可中斷
公平鎖: 按等待獲取鎖的線程的等待時(shí)間進(jìn)行獲取,等待時(shí)間長(zhǎng)的具有優(yōu)先獲取鎖權(quán)利
讀寫鎖:對(duì)資源讀取和寫入的時(shí)候拆分為2部分處理,讀的時(shí)候可以多線程一起讀,寫的時(shí)候必須同步地寫
2. synchronized與Lock的區(qū)別
2.1 兩者區(qū)別
| 類別 | synchronized | Lock |
|---|---|---|
| 存在層次 | Java的關(guān)鍵字,在jvm層面上 | 是一個(gè)類 |
| 鎖的釋放 | 1、以獲取鎖的線程執(zhí)行完同步代碼,釋放鎖 2、線程執(zhí)行發(fā)生異常,jvm會(huì)讓線程釋放鎖 |
在finally中必須釋放鎖,不然容易造成線程死鎖 |
| 鎖的獲取 | 假設(shè)A線程獲得鎖,B線程等待。 如果A線程阻塞,B線程會(huì)一直等待 |
分情況而定,Lock有多個(gè)鎖獲取的方式,具體下面會(huì)說道 大致就是可以嘗試獲得鎖,線程可以不用一直等待 |
| 鎖狀態(tài) | 無法判斷 | 可以判斷 |
| 鎖類型 | 可重入 不可中斷 非公平 | 可重入 可判斷 可公平(兩者皆可) |
| 性能 | 少量同步 | 大量同步 |
3. Lock詳細(xì)介紹與Demo
以下是Lock接口的源碼,筆者修剪之后的結(jié)果:
public interface Lock {
/**
* Acquires the lock.
*/
void lock();
/**
* Acquires the lock unless the current thread is
* {@linkplain Thread#interrupt interrupted}.
*/
void lockInterruptibly() throws InterruptedException;
/**
* Acquires the lock only if it is free at the time of invocation.
*/
boolean tryLock();
/**
* Acquires the lock if it is free within the given waiting time and the
* current thread has not been {@linkplain Thread#interrupt interrupted}.
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* Releases the lock.
*/
void unlock();
}
從Lock接口中我們可以看到主要有個(gè)方法,這些方法的功能從注釋中可以看出:
- lock():獲取鎖,如果鎖被暫用則一直等待
- unlock():釋放鎖
- tryLock(): 注意返回類型是boolean,如果獲取鎖的時(shí)候鎖被占用就返回false,否則返回true
- tryLock(long time, TimeUnit unit):比起tryLock()就是給了一個(gè)時(shí)間期限,保證等待參數(shù)時(shí)間
- lockInterruptibly():用該鎖的獲得方式,如果線程在獲取鎖的階段進(jìn)入了等待,那么可以中斷此線程,先去做別的事
通過 以上的解釋,大致可以解釋在上個(gè)部分中“鎖類型(lockInterruptibly())”,“鎖狀態(tài)(tryLock())”等問題,還有就是前面子所獲取的過程我所寫的“大致就是可以嘗試獲得鎖,線程可以不會(huì)一直等待”用了“可以”的原因。
下面是Lock一般使用的例子,注意ReentrantLock是Lock接口的實(shí)現(xiàn)。
lock():
package com.brickworkers;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
//需要參與同步的方法
private void method(Thread thread){
lock.lock();
try {
System.out.println("線程名"+thread.getName() + "獲得了鎖");
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名"+thread.getName() + "釋放了鎖");
lock.unlock();
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
//線程1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.method(Thread.currentThread());
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.method(Thread.currentThread());
}
}, "t2");
t1.start();
t2.start();
}
}
//執(zhí)行情況:線程名t1獲得了鎖
// 線程名t1釋放了鎖
// 線程名t2獲得了鎖
// 線程名t2釋放了鎖
tryLock():
package com.brickworkers;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
//需要參與同步的方法
private void method(Thread thread){
/* lock.lock();
try {
System.out.println("線程名"+thread.getName() + "獲得了鎖");
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名"+thread.getName() + "釋放了鎖");
lock.unlock();
}*/
if(lock.tryLock()){
try {
System.out.println("線程名"+thread.getName() + "獲得了鎖");
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名"+thread.getName() + "釋放了鎖");
lock.unlock();
}
}else{
System.out.println("我是"+Thread.currentThread().getName()+"有人占著鎖,我就不要啦");
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
//線程1
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.method(Thread.currentThread());
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.method(Thread.currentThread());
}
}, "t2");
t1.start();
t2.start();
}
}
//執(zhí)行結(jié)果: 線程名t2獲得了鎖
// 我是t1有人占著鎖,我就不要啦
// 線程名t2釋放了鎖
看到這里相信大家也都會(huì)使用如何使用Lock了吧,關(guān)于tryLock(long time, TimeUnit unit)和lockInterruptibly()不再贅述。前者主要存在一個(gè)等待時(shí)間,在測(cè)試代碼中寫入一個(gè)等待時(shí)間,后者主要是等待中斷,會(huì)拋出一個(gè)中斷異常,常用度不高,喜歡探究可以自己深入研究。
前面比較重提到“公平鎖”,在這里可以提一下ReentrantLock對(duì)于平衡鎖的定義,在源碼中有這么兩段:
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
從以上源碼可以看出在Lock中可以自己控制鎖是否公平,而且,默認(rèn)的是非公平鎖,以下是ReentrantLock的構(gòu)造函數(shù):
public ReentrantLock() {
sync = new NonfairSync();//默認(rèn)非公平鎖
}

浙公網(wǎng)安備 33010602011771號(hào)