您好,登錄后才能下訂單哦!
java中的線程同步是什么?相信大部分人都不太了解,今天小編為了讓大家更加了解線程同步,給大家總結(jié)了以下內(nèi)容,跟隨小編一起來看看吧。
線程同步
多線程之間調(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ì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()+"沒有獲取票源?。?!"); } } @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)窗口沒有獲取票源?。?! 售貨柜臺(tái):2號(hào)窗口沒有獲取票源?。?! 售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:9 售貨柜臺(tái):2號(hào)窗口沒有獲取票源!?。? 售貨柜臺(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)窗口沒有獲取票源?。。? 售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?! 售貨柜臺(tái):2號(hào)窗口獲取了票源+++++ 售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:6 售貨柜臺(tái):1號(hào)窗口獲取了票源+++++ 售貨柜臺(tái):2號(hào)窗口沒有獲取票源!?。? 售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。。? 售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:5 售貨柜臺(tái):2號(hào)窗口獲取了票源+++++ 售貨柜臺(tái):1號(hào)窗口沒有獲取票源?。?! 售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:4 售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。。? 售貨柜臺(tái):1號(hào)窗口獲取了票源+++++ 售貨柜臺(tái):2號(hào)窗口沒有獲取票源?。?! 售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?! 售貨柜臺(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)窗口沒有獲取票源!?。? 售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:1 售貨柜臺(tái):1號(hào)窗口獲取了票源+++++ 售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:0 售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?! 售貨柜臺(tái):2號(hào)窗口沒有獲取票源!?。? Process finished with exit code 0//沒有獲取到貨源的票口,就直接沒有等待,進(jìn)入下次買票
tryLock(long time, TimeUnit unit)
tryLock(long time, TimeUnit unit)可以設(shè)置拿不到鎖的時(shí)候等待一段時(shí)間。//第一個(gè)參數(shù)時(shí)常長,第二個(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() + "沒有獲取票源?。?!"); } } 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)窗口沒有獲取票源?。?! 售貨柜臺(tái):2號(hào)窗口獲取了票源+++++ 售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:8 售貨柜臺(tái):3號(hào)窗口沒有獲取票源!??! 售貨柜臺(tái):1號(hào)窗口沒有獲取票源!??! 售貨柜臺(tái):3號(hào)窗口獲取了票源+++++ 售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:7 售貨柜臺(tái):1號(hào)窗口沒有獲取票源!?。? 售貨柜臺(tái):2號(hào)窗口沒有獲取票源?。?! 售貨柜臺(tái):1號(hào)窗口獲取了票源+++++ 售貨柜臺(tái):1號(hào)窗口賣出了一張票,剩余:6 售貨柜臺(tái):2號(hào)窗口沒有獲取票源!?。? 售貨柜臺(tái):3號(hào)窗口沒有獲取票源?。?! 售貨柜臺(tái):2號(hào)窗口獲取了票源+++++ 售貨柜臺(tái):2號(hào)窗口賣出了一張票,剩余:5 售貨柜臺(tái):3號(hào)窗口沒有獲取票源!!! 售貨柜臺(tái):1號(hào)窗口沒有獲取票源!??! 售貨柜臺(tái):3號(hào)窗口獲取了票源+++++ 售貨柜臺(tái):3號(hào)窗口賣出了一張票,剩余:4 售貨柜臺(tái):1號(hào)窗口沒有獲取票源?。?! 售貨柜臺(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)窗口沒有獲取票源!??! 售貨柜臺(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)窗口沒有獲取票源?。?! 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)窗口沒有獲取票源?。?! 售貨柜臺(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)窗口沒有獲取票源!??! 售貨柜臺(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í)間加長: 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ù)邏輯寫起來也簡單,不需要手動(dòng)釋放鎖。
億速云,有大量免費(fèi)的JAVA入門教程,歡迎大家學(xué)習(xí)!
看完上述內(nèi)容,你們對(duì)java中的線程同步有進(jìn)一步的了解嗎?如果還想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。