前面介紹了進(jìn)程與線程的關(guān)系及其啟動(dòng)和銷毀方式以及線程中的知識(shí)點(diǎn)漾唉,使用多線程的時(shí)候會(huì)涉及到數(shù)據(jù)同步問題赵刑,本文梳理一下線程同步相關(guān)知識(shí)點(diǎn)般此。
線程同步
多線程之間調(diào)用同一對(duì)象時(shí),為了運(yùn)行的安全和準(zhǔn)確性科乎,需要對(duì)該對(duì)象進(jìn)行同步茅茂,確保每一個(gè)線程用到的時(shí)候該對(duì)象的結(jié)果都是正確的空闲,該對(duì)象的狀態(tài)都是合理的走敌,這部分涉及到同步碴倾、線程鎖等知識(shí)點(diǎn)。這部分的只是就涉及到了synchronized、同步鎖(Lock)的概念影斑。
synchronized
synchronized關(guān)鍵詞可以修飾對(duì)象给赞、方法,通常用法如下:
//同步代碼塊
synchronized(Object object){
...
}
//或者
//同步方法
public synchronized void test(){
...
}
其中有一個(gè)同步監(jiān)視器的概念矫户,比如上面同步代碼塊的object對(duì)象以及同步方法的this對(duì)象就會(huì)同步監(jiān)視,多個(gè)線程同時(shí)調(diào)用一個(gè)同步的代碼塊或者方法時(shí)皆辽,在任何時(shí)刻只能夠一個(gè)線程能夠獲得該同步監(jiān)視的對(duì)象鎖柑蛇,執(zhí)行完代碼之后才會(huì)釋放該鎖,在此期間其他調(diào)用的線程只能等待該鎖被釋放后才能調(diào)用驱闷。
上文中提到的SellRunnable類中的sell方法也用到了synchronized耻台,上文中代碼執(zhí)行太快,所以感知不到空另,如果修改一下就能明白有沒有synchronized的區(qū)別了盆耽。
public class ThreadTest {
public static void main(String[] args) {
SellRunnable sellRunnable = new SellRunnable();
Thread thread1 = new Thread(sellRunnable, "1");
Thread thread2 = new Thread(sellRunnable, "2");
Thread thread3 = new Thread(sellRunnable, "3");
thread2.start();
thread1.start();
thread3.start();
}
}
class SellRunnable implements Runnable {
//有十張票
int index = 10;
public void sell() {
if (index >= 1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
index--;
System.out.println("售貨窗口:" + Thread.currentThread().getName() + " 賣出了一張票,剩余:" + index);
} else {
System.out.println("售貨窗口:" + Thread.currentThread().getName() + " 買票時(shí)沒票了");
}
}
@Override
public void run() {
while (index > 0) {
System.out.println("售貨窗口:" + Thread.currentThread().getName() + " 開始買票");
sell();
}
}
}
//執(zhí)行結(jié)果:
售貨窗口:1 開始買票
售貨窗口:2 開始買票
售貨窗口:3 開始買票
售貨窗口:2 賣出了一張票,剩余:9
售貨窗口:2 開始買票
售貨窗口:1 賣出了一張票,剩余:9
售貨窗口:1 開始買票
售貨窗口:3 賣出了一張票,剩余:8
售貨窗口:3 開始買票
售貨窗口:1 賣出了一張票,剩余:6
售貨窗口:1 開始買票
售貨窗口:2 賣出了一張票,剩余:6
售貨窗口:2 開始買票
售貨窗口:3 賣出了一張票,剩余:5
售貨窗口:3 開始買票
售貨窗口:1 賣出了一張票,剩余:4
售貨窗口:1 開始買票
售貨窗口:2 賣出了一張票,剩余:3
售貨窗口:3 賣出了一張票,剩余:2
售貨窗口:3 開始買票
售貨窗口:2 開始買票
售貨窗口:3 賣出了一張票,剩余:1
售貨窗口:2 賣出了一張票,剩余:0
售貨窗口:1 賣出了一張票,剩余:1
Process finished with exit code 0 //可以看到,票數(shù)減少是錯(cuò)誤的
//sell方法添加synchronized修飾符后 執(zhí)行結(jié)果:
public synchronized void sell() {
if (index >= 1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
index--;
System.out.println("售貨窗口:" + Thread.currentThread().getName() + " 賣出了一張票,剩余:" + index);
} else {
System.out.println("售貨窗口:" + Thread.currentThread().getName() + " 買票時(shí)沒票了");
}
}
售貨窗口:2 開始買票
售貨窗口:3 開始買票
售貨窗口:1 開始買票
售貨窗口:2 賣出了一張票,剩余:9
售貨窗口:2 開始買票
售貨窗口:1 賣出了一張票,剩余:8
售貨窗口:1 開始買票
售貨窗口:3 賣出了一張票,剩余:7
售貨窗口:3 開始買票
售貨窗口:1 賣出了一張票,剩余:6
售貨窗口:1 開始買票
售貨窗口:2 賣出了一張票,剩余:5
售貨窗口:2 開始買票
售貨窗口:1 賣出了一張票,剩余:4
售貨窗口:1 開始買票
售貨窗口:1 賣出了一張票,剩余:3
售貨窗口:1 開始買票
售貨窗口:3 賣出了一張票,剩余:2
售貨窗口:3 開始買票
售貨窗口:1 賣出了一張票,剩余:1
售貨窗口:1 開始買票
售貨窗口:1 賣出了一張票,剩余:0
售貨窗口:2 買票時(shí)沒票了
售貨窗口:3 買票時(shí)沒票了
Process finished with exit code 0 // 可以看到扼菠,票數(shù)是正常減少的
以上對(duì)于sell方法進(jìn)行同步之后摄杂,在某一瞬間,只會(huì)有一個(gè)線程調(diào)用該方法循榆,所以里面判斷index的時(shí)候得到的結(jié)果就是正確的結(jié)果析恢。
以上同步的時(shí)候,是以降低運(yùn)行效率的方式來保證線程安全的秧饮,為此映挂,不要對(duì)線程使用類中沒必要的方法、對(duì)象進(jìn)行同步標(biāo)識(shí)盗尸,只對(duì)有競(jìng)爭(zhēng)的資源或者代碼進(jìn)行同步標(biāo)識(shí)柑船。
同步標(biāo)識(shí)后,有以下幾點(diǎn)可以釋放該鎖:
- 代碼塊振劳、方法執(zhí)行完畢(正常完畢椎组、return或break、拋出異常)
- 調(diào)用了wait方法历恐,使得當(dāng)前線程暫停寸癌。
當(dāng)線程執(zhí)行到同步代碼塊時(shí),sleep弱贼、yield方法不會(huì)釋放該同步鎖蒸苇,掛起方法suspend也不會(huì)(線程操作過程中盡量避免使用suspend、resume來操作線程狀態(tài)吮旅,容易導(dǎo)致死鎖溪烤。)
同步鎖Lock
上文中提到的synchronized是java中的一個(gè)關(guān)鍵詞味咳,也提到了在sleep的時(shí)候、進(jìn)行IO操作的時(shí)候該線程不會(huì)釋放線程鎖檬嘀,其他線程就需要一直等待槽驶,這樣有時(shí)會(huì)降低執(zhí)行的效率,所以就需要一個(gè)可以在線程阻塞時(shí)可以釋放線程鎖的替代方案鸳兽,Lock就是為了解決這個(gè)問題出現(xiàn)的掂铐。
Lock是一個(gè)java中的類,在java.util.concurrent.locks包中揍异,具體的代碼如下:
public interface Lock {
void lock();//加鎖
void lockInterruptibly() throws InterruptedException;//加鎖
boolean tryLock();//加鎖
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//加鎖
void unlock();//釋放鎖
Condition newCondition();//線程協(xié)作中用到
}
Lock接口的一個(gè)實(shí)現(xiàn)子類為ReentrantLock全陨,在java.util.concurrent.locks包下,ReentrantLock的源代碼如下:
public class ReentrantLock implements Lock, Serializable {
private static final long serialVersionUID = 7373984872572414699L;
private final ReentrantLock.Sync sync;
public ReentrantLock() {
this.sync = new ReentrantLock.NonfairSync();
}
public ReentrantLock(boolean var1) {//是否創(chuàng)建公平鎖
this.sync = (ReentrantLock.Sync)(var1?new ReentrantLock.FairSync():new ReentrantLock.NonfairSync());
}
public void lock() {
this.sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
this.sync.acquireInterruptibly(1);
}
public boolean tryLock() {
return this.sync.nonfairTryAcquire(1);
}
public boolean tryLock(long var1, TimeUnit var3) throws InterruptedException {
return this.sync.tryAcquireNanos(1, var3.toNanos(var1));
}
public void unlock() {
this.sync.release(1);
}
public Condition newCondition() {
return this.sync.newCondition();
}
public int getHoldCount() {//當(dāng)前線程持有該鎖的數(shù)量
return this.sync.getHoldCount();
}
public boolean isHeldByCurrentThread() {//該鎖是否被當(dāng)前線程持有
return this.sync.isHeldExclusively();
}
public boolean isLocked() {//是否被其他線程持有該鎖
return this.sync.isLocked();
}
public final boolean isFair() {//是否是公平鎖
return this.sync instanceof ReentrantLock.FairSync;
}
protected Thread getOwner() {//當(dāng)前鎖的持有線程
return this.sync.getOwner();
}
public final boolean hasQueuedThreads() {//是否有線程在等待該鎖
return this.sync.hasQueuedThreads();
}
public final boolean hasQueuedThread(Thread var1) {//目標(biāo)線程是否在等待該鎖
return this.sync.isQueued(var1);
}
public final int getQueueLength() {//等待該鎖線程的數(shù)量
return this.sync.getQueueLength();
}
protected Collection<Thread> getQueuedThreads() {//獲取所有等待該鎖的線程集合
return this.sync.getQueuedThreads();
}
...
}
Lock的使用方法
- lock
lock() 用來獲取鎖衷掷,如果該鎖被其他線程占用辱姨,則進(jìn)入等待。
public class LockTest {
public static void main(String[] args) {
com.test.java.SellRunnable sellRunnable = new com.test.java.SellRunnable();
Thread thread1 = new Thread(sellRunnable, "1號(hào)窗口");
Thread thread2 = new Thread(sellRunnable, "2號(hào)窗口");
Thread thread3 = new Thread(sellRunnable, "3號(hào)窗口");
thread1.start();
thread2.start();
thread3.start();
}
}
public class SellRunnable implements Runnable {
//有十張票
int index = 10;
Lock lock = new ReentrantLock();
public void sell() {
try {
lock.lock();
System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + "獲取了票源+++++");
if (index >= 1) {
index--;
System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + "賣出了一張票,剩余:" + index);
} else {
System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + "買票時(shí)沒票了000");
}
} finally {
lock.unlock();
}
}
@Override
public void run() {
while (index > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sell();
}
}
}
運(yùn)行結(jié)果:
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:9
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:8
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:7
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:3
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:2
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:1
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:0
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口買票時(shí)沒票了000
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口買票時(shí)沒票了000
Process finished with exit code 0 //每一個(gè)窗口都隨機(jī)獲取票源戚嗅、然后賣出票
- tryLock
tryLock()嘗試獲取鎖雨涛,如果獲取成功返回true,如果失敗渡处,則返回false镜悉,不會(huì)進(jìn)入等待狀態(tài)。
public class SellRunnable implements Runnable {
//有十張票
int index = 10;
Lock lock = new ReentrantLock();
public void sell() {
if (lock.tryLock()) {
try {
System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + "獲取了票源+++++");
if (index >= 1) {
index--;
System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + "賣出了一張票,剩余:" + index);
} else {
System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + "買票時(shí)沒票了000");
}
} finally {
lock.unlock();
}
} else {
System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + "沒有獲取票源R教薄!旧困!");
}
}
@Override
public void run() {
while (index > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sell();
}
}
}
運(yùn)行結(jié)果:
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口沒有獲取票源4挤荨!吼具!
售貨柜臺(tái):2號(hào)窗口沒有獲取票源A欧住!拗盒!
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:9
售貨柜臺(tái):2號(hào)窗口沒有獲取票源2澜摺!陡蝇!
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:8
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:7
售貨柜臺(tái):1號(hào)窗口沒有獲取票源H簟!登夫!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源9愠住!恼策!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口沒有獲取票源Q恢隆!!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源7滞佟?古觥!
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口沒有獲取票源U狼恰;∮!
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):3號(hào)窗口沒有獲取票源F础:慈馈!
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口沒有獲取票源0鞍>榫酢!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源6锰印5辽取!
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:3
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:2
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口沒有獲取票源3撂睢A屏ァ!
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:1
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:0
售貨柜臺(tái):3號(hào)窗口沒有獲取票源R砟帧0弑恰!
售貨柜臺(tái):2號(hào)窗口沒有獲取票源A攒<崛酢!
Process finished with exit code 0//沒有獲取到貨源的票口关摇,就直接沒有等待荒叶,進(jìn)入下次買票
- tryLock(long time, TimeUnit unit)
tryLock(long time, TimeUnit unit)可以設(shè)置拿不到鎖的時(shí)候等待一段時(shí)間。//第一個(gè)參數(shù)時(shí)常長(zhǎng)输虱,第二個(gè)參數(shù)時(shí)間單位
public class SellRunnable implements Runnable {
//有十張票
int index = 10;
Lock lock = new ReentrantLock();
public void sell() {
try {
if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) {
try {
System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + "獲取了票源+++++");
if (index >= 1) {
index--;
System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + "賣出了一張票,剩余:" + index);
} else {
System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + "買票時(shí)沒票了000");
}
try {
Thread.sleep(2000);//人為加入買票時(shí)間
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
} else {
System.out.println("售貨柜臺(tái):" + Thread.currentThread().getName() + "沒有獲取票源P╅埂!宪睹!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (index > 0) {
try {
Thread.sleep(500);//要不執(zhí)行太快愁茁,看不出效果
} catch (InterruptedException e) {
e.printStackTrace();
}
sell();
}
}
}
執(zhí)行結(jié)果:
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:9
售貨柜臺(tái):2號(hào)窗口沒有獲取票源!:岜ぁ埋市!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源!C道宅!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:8
售貨柜臺(tái):3號(hào)窗口沒有獲取票源J程!污茵!
售貨柜臺(tái):1號(hào)窗口沒有獲取票源S1ā!泞当!
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:7
售貨柜臺(tái):1號(hào)窗口沒有獲取票源<8颉!襟士!
售貨柜臺(tái):2號(hào)窗口沒有獲取票源5领!陋桂!
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):2號(hào)窗口沒有獲取票源D嫒ぁ!嗜历!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源P!梨州!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):3號(hào)窗口沒有獲取票源:鄞选!暴匠!
售貨柜臺(tái):1號(hào)窗口沒有獲取票源0盎帧!每窖!
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):1號(hào)窗口沒有獲取票源S行颉!岛请!
售貨柜臺(tái):2號(hào)窗口沒有獲取票源!>ā崇败!
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:3
售貨柜臺(tái):2號(hào)窗口沒有獲取票源!<缦椤后室!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源!;旌荨岸霹!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:2
售貨柜臺(tái):3號(hào)窗口沒有獲取票源!=取贡避!
售貨柜臺(tái):1號(hào)窗口沒有獲取票源M蠢琛!刮吧!
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:1
售貨柜臺(tái):1號(hào)窗口沒有獲取票源:ァ!杀捻!
售貨柜臺(tái):2號(hào)窗口沒有獲取票源>帷!致讥!
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:0
售貨柜臺(tái):2號(hào)窗口沒有獲取票源=銎汀!垢袱!
售貨柜臺(tái):3號(hào)窗口沒有獲取票源D拱荨!惶桐!
Process finished with exit code 0 //當(dāng)買票時(shí)間大約等待時(shí)間時(shí)撮弧,則沒有獲取票源的窗口不買票,進(jìn)入下個(gè)買票機(jī)會(huì)
將買票時(shí)間縮短:
try {
Thread.sleep(500);//人為加入買票時(shí)間
} catch (InterruptedException e) {
e.printStackTrace();
}
執(zhí)行結(jié)果:
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:9
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:8
售貨柜臺(tái):3號(hào)窗口沒有獲取票源R;哐堋!
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:7
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):3號(hào)窗口沒有獲取票源>群蕖C潮病!
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:3
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:2
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:1
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:0
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口買票時(shí)沒票了000
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口買票時(shí)沒票了000
Process finished with exit code 0 //等待時(shí)間內(nèi)獲取到票源了肠槽,也就賣出票了
- lockInterruptibly
lockInterruptibly()通過該方法獲取鎖時(shí)擎淤,如果該鎖正在被其他線程持有,則進(jìn)入等待狀態(tài)秸仙,但是這個(gè)等待過程是可以被中斷的嘴拢,通過調(diào)用Thread對(duì)象的interrupt方法就可中斷等待,中斷時(shí)拋出異常InterruptedException寂纪,需要捕獲或者聲明拋出席吴。
public class ThreadTest {
public static void main(String[] args) {
SellRunnable sellRunnable = new SellRunnable();
Thread thread1 = new Thread(sellRunnable, "1號(hào)窗口");
Thread thread2 = new Thread(sellRunnable, "2號(hào)窗口");
Thread thread3 = new Thread(sellRunnable, "3號(hào)窗口");
thread1.start();
try {
Thread.sleep(500);//確保窗口1號(hào)先獲取鎖
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
thread3.start();
try {
Thread.sleep(2000);//等待兩秒后,打斷窗口2捞蛋、3的等待
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.interrupt();
thread3.interrupt();
}
}
SellRunnable中等待時(shí)間加長(zhǎng):
try {
Thread.sleep(5000);//人為加入買票時(shí)間
} catch (InterruptedException e) {
e.printStackTrace();
}
執(zhí)行結(jié)果:
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:9
售貨柜臺(tái):3號(hào)窗口被打斷了 //這個(gè)地方被打斷了
售貨柜臺(tái):2號(hào)窗口被打斷了 //這個(gè)地方被打斷了
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:8
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:7
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:6
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:5
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:4
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:3
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:2
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:1
售貨柜臺(tái):1號(hào)窗口獲取了票源+++++
售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:0
售貨柜臺(tái):2號(hào)窗口獲取了票源+++++
售貨柜臺(tái):2號(hào)窗口買票時(shí)沒票了000
售貨柜臺(tái):3號(hào)窗口獲取了票源+++++
售貨柜臺(tái):3號(hào)窗口買票時(shí)沒票了000
Process finished with exit code 0
synchronized和Lock對(duì)比
通過以上代碼孝冒,可以看出Lock和synchronized的幾點(diǎn)關(guān)聯(lián)和區(qū)別:
- 兩者都是可重入鎖
可重入鎖是指當(dāng)一個(gè)線程獲得對(duì)象鎖之后,該線程可以再次獲取該對(duì)象的鎖而不被阻塞拟杉。比如同一個(gè)類中有多個(gè)方法(或一個(gè)方法遞歸調(diào)用)被synchronized修飾或者被Lock加持后庄涡,同一個(gè)線程在調(diào)用這兩個(gè)方法時(shí)都可以獲取該對(duì)象的鎖而不被阻塞。
不可重入鎖的示例:
public class Lock{
private boolean isLocked = false;
public void lock(){
while(isLocked){
wait();
}
isLocked = true;
}
public void unlock(){
isLocked = false;
notify();
}
}
//使用方法:
public class Test{
Lock lock = new Lock();
public void test1(){
lock.lock();
test2();
lock.unlock();
}
public void test2(){
lock.lock();
...
lock.unlock();
}
}
Test類在調(diào)用test1方法的時(shí)候搬设,執(zhí)行完lock.lock()后調(diào)用test2的時(shí)候穴店,就會(huì)一直等待撕捍,變成死鎖。
可重入鎖設(shè)計(jì)原理:
public class Lock{
private boolean isLocked = false;
private Thread lockedThread = null;
int lockedCount = 0;
public void lock(){
Thread thread = Thread.currentThread();
while(isLocked && thread != lockedThread){
wait();
}
isLocked = true;
lockedCount++;
lockedThread = thread;
}
public void unlock(){
Thread thread = Thread.currentThread();
if(thread == lockedThread){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
lockedThread = null;
notify();
}
}
}
}
這樣調(diào)用Test類的test1方法后迹鹅,test2方法也能順利被執(zhí)行绊序。
synchronized在實(shí)現(xiàn)上也基本上是采用記數(shù)器的方式來實(shí)現(xiàn)可重入的巫财。
- Lock是可中斷鎖冠胯,synchronized不可中斷啄育。
當(dāng)一個(gè)線程B執(zhí)行被鎖的對(duì)象的代碼時(shí),發(fā)現(xiàn)線程A已經(jīng)持有該鎖弟蚀,那么線程B就會(huì)進(jìn)入等待蚤霞,但是synchronized就無法中斷該等待過程,而Lock就可以通過lockInterruptibly方法拋出異常從而中斷等待义钉,去處理別的事情昧绣。
- Lock可創(chuàng)建公平鎖,synchronized是非公平鎖捶闸。
公平鎖的意思是按照請(qǐng)求的順序來獲取鎖夜畴,不平公鎖就無法保證線程獲取鎖的先后次序。
- Lock可以知道是否獲取到鎖删壮,synchronized不可以贪绘。
- synchronized在發(fā)生異常或者運(yùn)行完畢央碟,會(huì)自動(dòng)釋放線程占有的鎖税灌。而Lock需要主動(dòng)釋放鎖,否則會(huì)鎖死亿虽;
- synchronized在阻塞時(shí)菱涤,別的線程無法獲取鎖,Lock可以(這也是lock設(shè)計(jì)的一個(gè)目的)洛勉。
讀寫鎖
多個(gè)線程對(duì)同一個(gè)文件進(jìn)行寫操作時(shí)粘秆,會(huì)發(fā)生沖突所以需要加鎖,但是對(duì)同一個(gè)文件進(jìn)行讀操作的時(shí)候收毫,使用上面的方法會(huì)造成效率的降低翻擒,所以基于這種情況,產(chǎn)生了ReadWriteLock這個(gè)接口:
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading.
*/
Lock readLock();//讀的鎖
/**
* Returns the lock used for writing.
*
* @return the lock used for writing.
*/
Lock writeLock();//寫的鎖
}
這個(gè)接口的實(shí)現(xiàn)類是ReentrantReadWriteLock牛哺,其源代碼如下:
public class ReentrantReadWriteLock implements ReadWriteLock, Serializable {
private static final long serialVersionUID = -6992448646407690164L;
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
...
public ReentrantReadWriteLock.WriteLock writeLock() {//獲取write lock
return this.writerLock;
}
public ReentrantReadWriteLock.ReadLock readLock() {//獲取read lock
return this.readerLock;
}
...
}
使用方法和Lock一樣,使用到write時(shí)調(diào)用writeLock()方法獲取lock進(jìn)行加鎖劳吠,使用到read時(shí)調(diào)用readLock()方法進(jìn)行加鎖引润,需要注意的知識(shí)點(diǎn)如下:
- 線程A占用寫鎖,線程B在申請(qǐng)寫痒玩、讀的時(shí)候需要等待淳附。
- 線程A占用讀鎖议慰,線程B在申請(qǐng)寫操作時(shí),需要等待奴曙。
- 線程A占用讀鎖别凹,線程B獲取讀操作時(shí)可以獲取到。
總結(jié)
如果需要效率提升洽糟,則建議使用Lock炉菲,如果效率要求不高,則synchronized滿足使用條件坤溃,業(yè)務(wù)邏輯寫起來也簡(jiǎn)單拍霜,不需要手動(dòng)釋放鎖。