一、synchronize對象鎖和類鎖
synchronize為多線程關鍵字是一種同步鎖,它可以修飾以下幾種對象:
代碼塊:被修飾的代碼塊被稱為同步代碼塊,作用的范圍是{}里面的代碼,作用的對象是調用這個代碼塊的對象
方法:被修飾的方法稱為同步方法,作用的范圍是整個方法,作用的對象是調用這個方法的對象
類:作用的范圍是synchronize后面括號里的部分,作用的對象是當前這個類
1、對象鎖
下面由一個栗子引入:
public class TestSynchronize { //加了對象鎖的方法 private synchronized void syn(){ //自定義sleep工具類 SleepTools.second(2); System.out.println("syn is going..."+this.toString()); SleepTools.second(2); System.out.println("syn ended..."+this.toString()); } //調用了對象鎖方法的線程1 private static class thread implements Runnable{ private TestSynchronize testSynchronize; public thread(TestSynchronize testSynchronize){ this.testSynchronize = testSynchronize; } @Override public void run() { System.out.println("thread is running..."); testSynchronize.syn(); } } //調用了對象鎖方法的線程2 private static class thread2 implements Runnable{ private TestSynchronize testSynchronize; public thread2(TestSynchronize testSynchronize){ this.testSynchronize = testSynchronize; } @Override public void run() { System.out.println("thread2 is running..."); testSynchronize.syn(); } } public static void main(String[] args) { TestSynchronize testSynchronize = new TestSynchronize(); thread thread = new thread(testSynchronize); TestSynchronize testSynchronize2 = new TestSynchronize(); thread2 thread2 = new thread2(testSynchronize); //thread2 thread2 = new thread2(testSynchronize2); new Thread(thread).start(); new Thread(thread2).start(); } } /** 當兩個線程都將testSynchronize傳入時(即使用同一個對象調用加了對象鎖的方法)運行結果如下: thread is running... thread2 is running... syn is going...com.zl.synchronize.TestSynchronize@6b52350c syn ended...com.zl.synchronize.TestSynchronize@6b52350c syn is going...com.zl.synchronize.TestSynchronize@6b52350c syn ended...com.zl.synchronize.TestSynchronize@6b52350c */ /** 當一個傳入testSynchronize,另一個傳入testSynchronize2時 運行結果如下: thread is running... thread2 is running... syn is going...com.zl.synchronize.TestSynchronize@28835f5f syn is going...com.zl.synchronize.TestSynchronize@47c48106 syn ended...com.zl.synchronize.TestSynchronize@28835f5f syn ended...com.zl.synchronize.TestSynchronize@47c48106 */
結論:多個線程調用同一個對象的同步方法會阻塞,調用不同對象的同步方法不會阻塞
2、類鎖
1) synchronized修飾的靜態方法
public static synchronized void obj3() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } }
2) synchronized (test.class) ,鎖的對象是test.class,即test類的鎖。
public void obj1() { synchronized (test.class) { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } }
那么問題來了:在一個類中有兩方法,分別用synchronized 修飾的靜態方法(類鎖)和非靜態方法(對象鎖)。多線程訪問兩個方法的時候,線程會不會阻塞?
答案是當類鎖和對象鎖同時存在時,多線程訪問時不會阻塞,因為他們不是一個鎖。
二、volatile
volatile 是一個類型修飾符。volatile 的作用是作為指令關鍵字,確保本條指令不會因編譯器的優化而省略。
volatile的特性
三、ThreadLocal
下面給出測試程序:
public class ThreadLocalDemo { private static ThreadLocal<Integer> number = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { return 1; } }; private static class thread extends Thread{ @Override public void run() { Integer number = ThreadLocalDemo.number.get(); for (int i = 0; i < this.getId(); i++) { number++; } System.out.println(this.getName()+"---"+this.getId()+"===="+number); } } private static class thread2 extends Thread{ @Override public void run() { Integer number = ThreadLocalDemo.number.get(); for (int i = 0; i < this.getId(); i++) { number++; } System.out.println(this.getName()+"---"+this.getId()+"===="+number); } } public static void main(String[] args) { new Thread(new thread()).start(); new Thread(new thread2()).start(); } } /** Thread-0---12====13 Thread-2---14====15 */
四、等待(Wait)和通知(notify)
為了支撐多線程之間的協作,JDK提供了兩個非常重要的線程接口:等待wait()方法和通知notify()方法。 這兩個方法并不是在Thread類中的,而是輸出在Object類。這意味著任何對象都可以調用這兩個方法。
等待/通知的經典范式
wait()方法和notify()方法究竟是如何工作的呢?
如果一個線程調用了object.wait()方法,那么它就會進入object對象的等待隊列,這個隊列中,可能會有多個線程,因為系統運行多個線程同時等待某一個對象,
當object.notify()方法被調用的時候,它就會從這個等待隊列中隨機選擇一個線程,并進行喚醒。
除notity()方法外,Object對象還有一個類似的notifyAll()方法,它和notity方法的功能基本一致,不同的是,它會喚醒在這個等待隊列中所有等待的線程,而不是隨機一個。
object.wait()方法并不能隨便調用。它必須包含在對象的synchronzied語句中,無論是wait()方法或者notity()方法都需要首先獲得目標對象的一個監視器。
假設有T1和T2表示兩個線程,T1在正確執行wait()方法前,必須獲得object對象的監視器,而wait()方法執行之后會釋放這個監視器。
這樣做的目的是使其他等待在object對象上的線程不至于因為T1的休眠而全部無法正常執行。
線程T2在notity()方法調用前,也必須獲得object對象的監視器。此時T1已經釋放了這個監視器。所以T2可以順利獲得object對象的監視器。
接著,T2執行了notify()方法嘗試喚醒一個等待線程,這里假設喚醒了T1,T1被喚醒后,要做的第一件事并不是執行后續代碼,而是要嘗試重新
獲得object對象的監視器,而這個監視器也正是T1在wait()方法執行前所持有的那個。
如果暫時無法獲得,則T1還必須等待這個監視器。當監視器順利獲得后,T1才可以在真正意義上繼續執行。
這里要注意,只有當wait()和notify()被包含的synchronized語句執行完,才會釋放監視器。
為了方便理解,簡單的案例:
public class testWaitAndNotify { final static Object object = new Object(); public static class T1 extends Thread { public void run() { synchronized (object) { try { System.out.println(System.currentTimeMillis() + ":T1 start! "); System.out.println(System.currentTimeMillis() + ":T1 wait for object"); object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + ":T1 end! "); } } } public static class T2 extends Thread { public void run() { synchronized (object) { try { System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread"); object.notify(); sleep(5000); System.out.println(System.currentTimeMillis() + ":T2 end! "); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { Thread t1 = new T1(); Thread t2 = new T2(); t1.start(); t2.start(); } } /** 1571039516250:T1 start! 1571039516250:T1 wait for object 1571039516251:T2 start! notify one thread 1571039521251:T2 end! 1571039521251:T1 end! */
五、等待超時模式
由于經典的等待/通知范式無法做到超時等待,也就是說,當消費者在獲得鎖后,如果條件不滿足,等待生產者改變條件之前會一直處于等待狀態,在一些實際應用中,會浪費資源,降低運行效率。
偽代碼如下所示:
//假設超時時間是mills,則等待持續時間是remaining,超時時間是future long future = System.currentTimeMillis() + mills; long remaining = mills; synchronized (lock) { while (!condition && remaining > 0) { wait(remaining); remaining = future - System.currentTimeMillis(); } //處理代碼 }
六、join()
join在線程里面意味著“插隊”,哪個線程調用join代表哪個線程插隊先執行——但是插誰的隊是有講究了,不是說你可以插到隊頭去做第一個吃螃蟹的人,而是插到在當前運行線程的前面,比如系統目前運行線程A,在線程A里面調用了線程B.join方法,則接下來線程B會搶先在線程A面前執行,等到線程B全部執行完后才繼續執行線程A。
話不多說上代碼
public class TestJoin { private static class thread extends Thread{ private Thread t; //接收一個插隊線程 public thread(Thread t){ this.t = t; } @Override public void run() { try { //調用插隊線程的join方法 t.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName()+"---執行完畢!"); } } public static void main(String[] args) throws InterruptedException { //獲取當前線程作為前一個線程 Thread pre = Thread.currentThread(); //創建五個線程 for (int i = 0; i < 5; i++) { Thread thread = new Thread(new thread(pre),String.valueOf(i)); //啟動線程 thread.start(); //重置前一個線程 pre = thread; } System.out.println(System.currentTimeMillis()); //讓主線程睡眠2s Thread.currentThread().sleep(2000); System.out.println(System.currentTimeMillis()); System.out.println(Thread.currentThread().getName()+"---執行完畢"); } } /** 1571061168064 1571061170065 main---執行完畢 Thread-0---執行完畢! Thread-1---執行完畢! Thread-2---執行完畢! Thread-3---執行完畢! Thread-4---執行完畢! */
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。