多線程之鎖機制
前言
在Java並發編程實戰,會經常遇到多個線程訪問同一個資源的情況,這個時候就需要維護數據的一致性,否則會出現各種數據錯誤,其中一種同步方式就是利用Synchronized關鍵字執行鎖機制,鎖機制是先給共享資源上鎖,只有拿到鎖的線程才可以訪問共享資源,其他線程進入等待狀態。下面將以實例代碼講解一下
一、wait()、nofity()、nofityAll()講解
示例代碼
package thread; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit;/** * Created by StoneGeek on 2018/5/19. * 博客地址:http://www.cnblogs.com/sxkgeek * 當線程執行wait()時,會把當前的鎖釋放,然後讓出CPU,進入等待狀態。 * 當線程執行notify()/notifyAll()方法時,會喚醒一個處於等待狀態該對象鎖的線程,然後繼續往下執行,直到執行完退出對象鎖鎖住的區域(synchronized修飾的代碼塊)後再釋放鎖 * 個人認為synachronized(){}執行完後會釋放鎖 */ public class WaitNotify { static boolean flag = true; static Object lock = new Object(); public static void main(String[] args) throws Exception { Thread waitThread = new Thread(new Wait(), "WaitThread"); waitThread.start(); TimeUnit.SECONDS.sleep(1); Thread notifyThread = new Thread(new Notify(), "NotifyThread"); notifyThread.start(); } static class Wait implements Runnable { public void run() { // 加鎖,擁有lock的Monitor synchronized (lock) { // 當條件不滿足時,繼續wait,同時釋放了lock的鎖 while (flag) { System.out.println(Thread.currentThread().getName() + " flag is true. wait@ " + new SimpleDateFormat("HH:mm:ss") .format(new Date())); try { lock.wait(); System.out.println("此處繼續執行"+Thread.currentThread().getName()); // flag=true; } catch (InterruptedException e) { e.printStackTrace(); } } // 條件滿足時,完成工作 System.out.println(Thread.currentThread().getName() + " flag is false. running@ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } synchronized (lock){ System.out.println(Thread.currentThread().getName()+"執行結束"); } } } // wait()會立刻釋放synchronized(obj)中的obj鎖,以便其他線程可以執行obj.notify() // 但是notify()不會立刻立刻釋放sycronized(obj)中的obj鎖,必須要等notify()所在線程執行完synchronized(obj)塊中的所有代碼才會釋放這把鎖. // yield(),sleep()不會釋放鎖 static class Notify implements Runnable { public void run() { // 加鎖,擁有lock的Monitor synchronized (lock) { // 獲取lock的鎖,然後進行通知,通知時不會釋放lock的鎖, // 直到當前線程釋放了lock後,WaitThread才能從wait方法中返回 System.out.println(Thread.currentThread().getName() + " hold lock. notify @ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); lock.notifyAll(); flag = false; } // 再次加鎖 synchronized (lock) { System.out.println(Thread.currentThread().getName() + " hold lock again. sleep@ " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } synchronized (lock){ System.out.println(Thread.currentThread().getName()+"執行結束"); } } } }
執行結果如下
1 WaitThread flag is true. wait@ 20:50:39 2 NotifyThread hold lock. notify @ 20:50:40 3 NotifyThread hold lock again. sleep@ 20:50:40 4 NotifyThread執行結束 5 此處繼續執行WaitThread 6 WaitThread flag is false. running@ 20:50:40 7 WaitThread執行結束
解釋:
首先創建一個lock對象,然後給這個lock上鎖來對多個進程同步,flag是一個標誌,用來跳出while循環。
當線程執行wait()時,會把當前的鎖釋放,然後讓出CPU,進入等待狀態。
此時輪到notifythread線程,並且執行notifyAll(),這個意思是能夠喚醒所有正在等待這個lock對象的monitor的線程,但是
必須要等notify()所在線程執行完synchronized(obj)塊中的所有代碼才會釋放這把鎖,
此時接著waitthread被喚醒,繼續執行while循環,執行完之後,由於flag在notifythread中置為false,所以跳出while循環(如果在實例代碼的wail()後加flag=true結果是截然不同,由於notirythread進程執行完,此時會一直陷入wait,大家可以試試),
執行console打印5 6 7
notify()與notifyAll()的區別
notify()方法能夠喚醒一個正在等待該對象的monitor的線程,當有多個線程都在等待該對象
的monitor的話,則只能喚醒其中一個線程
而調用notifyAll()方法能夠喚醒所有正在等待這個對象的monitor的線程
當時的疑惑
(1)既然notify或者notifyAll需要執行完synchronized塊中的內容,那麽他還有什麽存在的價值的
後來執行完之後,才發現要是沒有這個方法,那麽synchronized塊執行完之後,waitthread還是在等待狀態,無法被喚醒。
(2)wait被notify喚醒之後,是接著執行,所以console打印5,並不是從頭執行(如果在實例代碼的wail()後加flag=true結果是截然不同,由於notirythread進程執行完,waitthread進程重新執行wait方法,此時會一直陷入wait,無其他進程喚醒此進程)
二、sychronized(object)跟sychroized(this)的區別、使用場景
(1)首先創建一個objeck,然後sychronzied(object){}
static Object lock = new Object(); synchronized (lock) {}
這種情況是當多個線程執行不同的代碼但是希望分別執行,不同時進行,當然可以加wait、notify來進行鎖機制同步
(2)sychronized(this)
synchronized (lock) {}
這種情況是多個線程執行相同的代碼,但是希望分別執行.
三、wait()/wait(long)和sleep(long)方法的區別
將示例代碼中的lock.wait()改為Thread.sleep(1000),console打印
WaitThread flag is true. wait@ 21:29:49 此處繼續執行WaitThread WaitThread flag is true. wait@ 21:29:50 此處繼續執行WaitThread WaitThread flag is true. wait@ 21:29:51 此處繼續執行WaitThread WaitThread flag is true. wait@ 21:29:52 此處繼續執行WaitThread
由此說明sleep並沒有釋放鎖。
區別:
1、Sleep(long)是Thread的方法,而wait()/wait(long)是Object的方法
2、Sleep(long)可以放在sychnoized塊內也可以不放在裏面,但是wait()/wait(long)必須放在語句塊內
3、Sleep(long)不釋放鎖,只是讓當前線程暫停一段時間,而wait()/wait(long)是釋放鎖
4、wait()將當前線程放到阻塞隊列,只有調用notify()/notifyAll()方法後,才將其從阻塞隊列中移動到就緒隊列,等待被CPU調度,而wait(long)方法執行後就是放到阻塞隊列,等待時間到期或者被wait()/wait(long)喚醒後就可以放到就緒隊列被CPU調度
目前還有一個疑惑,就是線程中的run方法有兩個同樣的synchroized(lock),是不是跟一個synchroized(lock)效果是一樣的,目前就運行結果來看是這樣子的!
多線程之鎖機制