本篇文章主要是總結Java多線程/高並發編程的知識點,由淺入深,僅作自己的學習筆記,部分侵刪。
一 . 基礎知識點
1. 進程於線程的概念
2.線程創建的兩種方式

注:public void run()方法提供了線程實際工作的代碼;
繼承Thread類的方法存在單繼承的缺陷;
Runnable的代碼可以被多個線程(Thread實例)共享,適合於多個線程處理統一資源的情況;
3. Thread類的常用方法

4. Java線程正確停止的方法
最常用的方法是設置退出旗標(標志),要注意,stop方法不是正確的方法,它會讓線程戛然而止,interrupt()方法也不是正確停止線程的方法,它的初衷並不是用於停止線程;
5. 線程交互:互斥與同步
互斥:同一時間只能有一個線程去對臨界區進行操作,通過synchronized(intrinsic lock)實現,只有獲得lock的線程才能進入synchronized聲明的代碼塊;
同步:線程之間的通信機制;當某些條件不具備時線程處於等待狀態,條件滿足,需要發出消息來喚醒所有線程;通過wait()/notify()/notifyAll()實現。
Wait Set:可以理解為線程休息室;
共享資源/數據又被稱為臨界區(Critical Section),當有一個線程想要訪問共享資源的時候,首先,他需要獲得鎖,獲得鎖后進入我們的臨界區進行操作,操作過程中,如果發現某些情況不被滿足,將調用鎖對象上的wait()方法,此時,該線程釋放掉鎖資源,然后進入到鎖對象上的Wait Set,由於這個線程釋放掉了我們的鎖資源,使得其他線程可以來競爭多資源,所以其他線程獲得鎖並進入臨界區,同時在鎖對象上的wait set中有多條線程在等待條件的滿足,當我們的當前運行線程執行完某些操作需要通知等待的線程時,調用notify方法將會喚醒所資源所持有的等待區域中的一條線程,是這條線程有機會去進行競爭CPU資源;或者調用notifyAll方法,這時會使wait set中的所有線程被喚醒,從而使得它們有機會在當前線程離開臨界資源釋放鎖之后去競爭臨界資源的鎖對象。
6. 線程的生命周期

7. 守護線程
Java線程有兩類:
用戶線程:運行在前台,執行具體的任務;程序的主線程,連接網絡的子線程等都是用戶線程;
守護線程:運行在后台,為其他前台線程服務;
守護線程的特點:一旦所有用戶線程都結束運行,守護線程會隨JVm一起結束工作;
守護線程的應用:數據庫連接池中的監測線程;JVM虛擬機啟動后的監測線程,最常見的是垃圾回收線程;
守護線程的設置:通過調用Thread類的setDaemon(true)方法來設置當前的線程為守護線程;
注意:setDaemon(true)必須在start()方法之前調用,否則會拋出IllegalThreadStateException異常;守護線程中產生的新線程也是守護線程;不是所有的任務都可以分配給守護線程來執行,比如讀寫操作或者計算邏輯;
二 . 進階知識點
1. 對象加鎖
1.1 使用synchronized關鍵字對某個對象加鎖
1 public class T { 2 private int count=10; 3 private Object o = new Object(); 4 public void m(){ 5 synchronized (o){ //任何線程要執行下面代碼,必須先拿到o的鎖 6 count--; 7 System.out.println(Thread.currentThread().getName()+"count="+count); 8 }//執行完了釋放鎖 9 } 10 }

在上面的代碼中,鎖是自己new的,每次new出一個沒有其他功能的對象比較麻煩,所以可以用synchronized(this),代碼如下:
1 public class T { 2 private int count = 10; 3 public void m(){ 4 synchronized (this){ 5 count--; 6 System.out.println(Thread.currentThread().getName()+"count="+count); 7 } 8 /** 9 * 上面的代碼可以簡寫為: 10 * public synchronized void m(){ 11 * count--; 12 * System.out.println(Thread.currentThread().getName()+"count="+count); 13 * } 14 * 注意:synchronized 鎖定的是對象而不是代碼 15 */ 16 17 } 18 }
1.2 synchronized用在靜態方法上
類中靜態方法和靜態屬性屬性是不需要new出對象來訪問的,沒有new出來,就沒有this引用的存在,所以當鎖定一個靜態方法時,相當於鎖定的是當前類的class對象。
1 public class T { 2 private static int count = 10; 3 public synchronized static void m(){ //這里等同於synchronized(packagename.T.class) 4 count--; 5 System.out.println(Thread.currentThread().getName()+"count="+count); 6 } 7 8 public static void nm(){ 9 synchronized (T.class){ //是class類的對象 10 count--; 11 } 12 } 13 }
1.3 鎖住線程的run方法
1 public class T implements Runnable{ 2 private int count = 10; 3 public synchronized void run(){ 4 count--; 5 System.out.println(Thread.currentThread().getName()+"count="+count); 6 } 7 public static void main(String[] args){ 8 T t = new T();//多個線程共同訪問一個Runnable對象 9 for(int i=0; i<5; i++){ 10 new Thread(t,"Thread"+i).start(); 11 } 12 } 13 }
如果不加synchronized時,count--和打印語句間可能有別的線程執行count--,導致前后數據不一致,加synchronized后方法里的所有語句相當於一個原子操作,只有當run方法執行完了釋放鎖,下一個線程才能拿到鎖執行run方法。
1.4 面試題:同步方法和非同步方法是否可以同時調用?-->可以
1 public class T { 2 public synchronized void m1(){ 3 System.out.println(Thread.currentThread().getName()+"m1 start..."); 4 try { 5 Thread.sleep(10000); 6 }catch(InterruptedException e){ 7 e.printStackTrace(); 8 } 9 System.out.println(Thread.currentThread().getName()+"m1 end..."); 10 11 } 12 public void m2(){ 13 try{ 14 Thread.sleep(5000); 15 }catch(InterruptedException e){ 16 e.printStackTrace(); 17 } 18 System.out.println(Thread.currentThread().getName()+"m2"); 19 } 20 21 public static void main(String[] args){ 22 T t = new T(); 23 new Thread(t::m1,"t1").start(); 24 new Thread(t::m2,"t2").start(); 25 } 26 }
執行結果:
t1m1 start...
t2m2
t1m1 end...
1.5 對業務寫方法加鎖,對業務讀方法不加鎖,容易產生臟讀
1 public class Account { 2 String name; 3 double balance; 4 public synchronized void set(String name,double balance){ 5 this.name = name; 6 try{ 7 Thread.sleep(2000); 8 }catch(InterruptedException e){ 9 e.printStackTrace(); 10 } 11 this.balance = balance; 12 } 13 14 public double getBalance(String name){ 15 return this.balance; 16 } 17 18 public static void main(String[] args){ 19 Account a = new Account(); 20 new Thread(()->a.set("zhangsan",100.0)).start(); 21 try{ 22 TimeUnit.SECONDS.sleep(2); 23 }catch(InterruptedException e){ 24 e.printStackTrace(); 25 } 26 System.out.println(a.getBalance("zhangsan"));//0.0 27 28 try{ 29 TimeUnit.SECONDS.sleep(2); 30 }catch(InterruptedException e){ 31 e.printStackTrace(); 32 } 33 System.out.println(a.getBalance("zhangsan"));//100.0 34 } 35 }
1.6 一個同步方法可以調用另一個同步方法
一個線程已經擁有某個對象的鎖,再次申請時仍然會得到該對象的鎖
synchronized獲得的鎖是可重入的
1 public class T { 2 synchronized void m1(){ 3 System.out.println("m1 start"); 4 try{ 5 TimeUnit.SECONDS.sleep(1); 6 }catch(InterruptedException e){ 7 e.printStackTrace(); 8 } 9 m2();//可調用 10 } 11 synchronized void m2(){ 12 try{ 13 TimeUnit.SECONDS.sleep(2); 14 }catch(InterruptedException e){ 15 e.printStackTrace(); 16 } 17 System.out.println("m2"); 18 } 19 }
1.7 在繼承中,子類重寫的同步方法可以調用父類的同步方法
1 public class T { 2 synchronized void m(){ 3 System.out.println("m start"); 4 try{ 5 TimeUnit.SECONDS.sleep(1); 6 }catch(InterruptedException e){ 7 e.printStackTrace(); 8 } 9 System.out.println("m end"); 10 } 11 public static void main(String[] args){ 12 new TT().m(); 13 } 14 15 } 16 class TT extends T{ 17 synchronized void m(){ 18 System.out.println("child m start"); 19 super.m();//可以調用父類的同步方法 20 System.out.println("child m end"); 21 } 22 }

1.8 出現異常,默認情況下鎖會被釋放
1 /** 2 * 程序在執行過程中如果出現異常,默認情況下鎖會被釋放 3 * 所以在執行並發處理的過程中,有異常要小心,不然可能會發生不一致的情況; 4 * 比如,在一個web app處理過程中,多個servlet線程共同訪問一個資源 5 * 這是如果異常處理不合適,在第一個線程中拋出異常,其他線程就會進入同步代碼區, 6 * 有可能會訪問到異常產生時的數據(處理了一半的數據), 7 * 因此要非常小心處理同步業務邏輯中的異常 8 */ 9 10 import java.util.concurrent.TimeUnit; 11 public class T { 12 int count = 0; 13 synchronized void m(){ 14 System.out.println(Thread.currentThread().getName()+"start"); 15 while(true){ 16 count++; 17 System.out.println(Thread.currentThread().getName()+"count"+count); 18 try{ 19 TimeUnit.SECONDS.sleep(1); 20 }catch(InterruptedException e){ 21 e.printStackTrace(); 22 } 23 if(count==5){ 24 int i=1/0;//此處拋出異常,鎖被釋放,要想不被釋放,可以在這里進行catch,然后讓循環繼續 25 26 } 27 } 28 } 29 public static void main(String[] args){ 30 T t = new T(); 31 Runnable r = new Runnable(){ 32 public void run(){ 33 t.m(); 34 } 35 }; 36 new Thread(r,"t1").start(); 37 try{ 38 TimeUnit.SECONDS.sleep(3); 39 }catch(InterruptedException e){ 40 e.printStackTrace(); 41 } 42 new Thread(r,"t2").start(); 43 } 44 }

2. volatile 關鍵字
2.1 volatile的可見性
volatile 關鍵字,使一個變量在多個線程間可見,A B 線程都用到一個變量,java默認是A線程中保存一份copy,這樣如果B線程修改了該變量,則A未必知道,使用volatile關鍵字,會讓所有線程都會讀到變量的修改值;
在下面的代碼中,running 是存在於堆內存對象中,當線程t1開始運行的時候,會把running值從內存中讀到t1線程工作區,在運行過程中直接使用這個copy並不會每次都去讀取堆內存,這樣,當主線程修改running的值之后,t1線程感知不到,所以不會停止運行
volatile並不能保證多個線程共同修改running變量時所帶來的不一致問題,也就是說 volatile不能代替synchronized;
1 public class T { 2 volatile boolean running = true; 3 void m(){ 4 System.out.println("m start"); 5 while(running){ 6 7 } 8 System.out.println("m end"); 9 } 10 public static void main(String[] args){ 11 T t = new T(); 12 new Thread(t::m,"t1").start(); 13 try{ 14 TimeUnit.SECONDS.sleep(1); 15 }catch(InterruptedException e){ 16 e.printStackTrace(); 17 } 18 t.running = false; 19 } 20 }

volatile 只保證了原子性,而synchronized既保證可見性有保證原子性,但是synchronized太重了;
2.2 volatile不具有原子性
1 /*10個線程分別執行10000次count++,count是對象vna的成員變量,按理來說最終count=100000, 2 但是最終每次執行結果都不一樣,count一直小於100000,說明volatile不具備原子性*/ 3 public class VolatileNoAtomic { 4 volatile int count = 0; 5 void m() { 6 for(int i=0; i<10000; i++) { 7 count ++ ; 8 } 9 } 10 public static void main(String[] args) { 11 VolatileNoAtomic vna = new VolatileNoAtomic(); 12 List<Thread> threads = new ArrayList<>(); 13 for (int i=0; i<10; i++) { 14 threads.add(new Thread(vna::m, "thread" + i)); 15 } 16 threads.forEach(o->o.start()); 17 threads.forEach((o)->{ 18 try { 19 //join()方法阻塞調用此方法的線程,直到線程t完成,此線程再繼續。通常用於在main()主線程內,等待其它線程完成再結束main()主線程。 20 o.join(); //相當於在main線程中同步o線程,o執行完了,main線程才有執行的機會 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 }); 25 System.out.println(vna.count); 26 } 27 }
2.3 synchronized既保證原子性又保證可見性
1 public class SynVisibleAndAtomic { 2 int count = 0; 3 synchronized void m() { //m方法加了synchronized修飾,保證了原子性和可見性 4 for (int i=0; i<10000; i++) { 5 count ++ ; 6 } 7 } 8 public static void main(String[] args) { 9 SynVisibleAndAtomic sva = new SynVisibleAndAtomic(); 10 List<Thread> threads = new ArrayList<>(); 11 for (int i=0; i<10; i++) { 12 threads.add(new Thread( sva::m , "thread-" + i)); 13 } 14 threads.forEach((o)->o.start()); 15 threads.forEach((o)-> { 16 try { 17 o.join(); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 }); 22 System.out.println(sva.count); //100000 23 } 24 }
2.4 面試題:實現一個容器,提供兩個方法add,size,線程1添加10個元素到容器,線程2監控元素的個數,當個數為5時,線程2提示並結束
1 public class MyContainerVolatile { 2 volatile List list = new ArrayList(); 3 public void add(Object o) { //add 4 list.add(o); 5 } 6 public int size() { //size 7 return list.size(); 8 } 9 10 public static void main(String[] args) { 11 MyContainerVolatile mcv = new MyContainerVolatile(); 12 new Thread( () -> { //該線程負責往list里添加 13 for (int i=0; i<10; i++) { 14 mcv.add(new Object()); 15 System.out.print(" add-" + i); 16 try { 17 Thread.sleep(500); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 } 22 },"t1").start(); 23 new Thread( () -> { //該線程一直監測list的size,直到size=5 24 while(true) { //一直監測着,很浪費CPU 25 if(mcv.size() == 5) { //此處未加同步,仍然可能會出現t1中又一次++為6了,才break 26 break; 27 } 28 } 29 System.out.print(" t2結束 "); 30 },"t2").start(); 31 } 32 }
上面的代碼能解決問題,但t2線程的死循環很浪費CPU,影響性能!更優方法是使用wait和notify,請參看下例:
1 public class MyContainerWaitNotify { 2 volatile List list = new ArrayList(); 3 public void add(Object o) { 4 list.add(o); 5 } 6 public int size() { 7 return list.size(); 8 } 9 10 public static void main(String[] args) { 11 MyContainerWaitNotify mcwn = new MyContainerWaitNotify(); 12 final Object lock = new Object(); 13 new Thread(()->{ 14 synchronized (lock) { 15 System.out.print(" ***線程t2啟動*** "); 16 if (mcwn.size() != 5) { 17 try { 18 lock.wait(); //size不等於5時,就一直在那等着,直到被t1叫醒 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 } 23 System.out.print(" ***線程t2結束*** "); 24 lock.notify(); //通知t1繼續執行 25 } 26 }, "t2").start(); 27 28 new Thread(()->{ 29 synchronized (lock) { 30 for(int i=0; i<10; i++) { 31 mcwn.add(new Object()); 32 System.out.print(" add-" + i); 33 if (mcwn.size() == 5) { 34 lock.notify(); //喚醒另一個線程t2,本線程繼續執行,直至synchronized包裹的代碼塊結束或者調用了wait 35 try { 36 lock.wait(); //釋放鎖,讓t2得以執行 37 } catch (InterruptedException e) { 38 e.printStackTrace(); 39 } 40 } 41 try { 42 Thread.sleep(1000); 43 } catch (InterruptedException e) { 44 e.printStackTrace(); 45 } 46 } 47 } 48 },"t1").start(); 49 } 50 }
注意:wait會釋放鎖,但notify不會,本代碼會執行到wait或synchronized塊結束才釋放鎖;
但是,上述的方法還是過於繁瑣,Java提供了門閂;
使用CountDownLatch(門閂)的await和countdown方法替代wait和notify方法來進行通知:
1 public class MyContainerLatch { 2 volatile List list = new ArrayList(); //添加volatile,使t2能夠得到通知 3 public void add(Object o) { 4 list.add(o); 5 } 6 public int size() { 7 return list.size(); 8 } 9 10 public static void main(String[] args) { 11 MyContainerLatch mcl = new MyContainerLatch(); 12 CountDownLatch latch = new CountDownLatch(1); //當1變成0時,門就開了 13 new Thread(() -> { 14 System.out.print(" *t2啟動* "); 15 if (mcl.size() != 5) { 16 try { 17 latch.await(); //等待不需要鎖定一個對象 18 //latch.await(5000,TimeUnit.MILLISECONDS); //也可以指定等待時間 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 } 23 System.out.print(" *t2結束* "); 24 },"t2").start(); 25 try { 26 TimeUnit.SECONDS.sleep(1); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 31 new Thread(()->{ 32 System.out.print(" *t1啟動* "); 33 for (int i=0; i<10; i++) { 34 mcl.add(new Object()); 35 System.out.print(" add-" + i); 36 if (mcl.size() == 5) { 37 latch.countDown(); //打開門閂,讓t2得以執行。調用一次countDown,就減1 38 } 39 try { 40 TimeUnit.SECONDS.sleep(1); 41 } catch (InterruptedException e) { 42 e.printStackTrace(); 43 } 44 } 45 System.out.print(" *t1結束* "); 46 47 },"t1").start(); 48 } 49 }
這樣做的好處是通信方式簡單,同時也可以指定等待時間。
聲明一個CountDownLatch對象latch並設定初始值>0,調用latch.countDown()使latch值減一,當latch值變為0,門閂打開;
當不涉及同步,只涉及鎖定,用synchronized+wait/notify就顯得太重了。這時應該考慮CountDownLatch/cyclicbarrier/semephore.
3. 原子變量類(AtomXxx)
原子變量最主要的一個特點是所有的操作都是原子的,synchronized關鍵字也能做到對變量的原子操作,但是成本相對較高,需要獲取鎖對象,釋放鎖對象,如果不能獲取到鎖,還需要阻塞在阻塞隊列上進行等待:
1 public class Counter { 2 private int count; 3 4 public synchronized void addCount(){ 5 this.count++; 6 } 7 }
上述代碼使用synchronized關鍵字保證變量原子性,簡單的count++操作,線程首先需要獲得Counter類實例的對象鎖,然后完成自增操作,最后釋放鎖,十分消耗成本,如果不能獲取到鎖,還需要阻塞線程等;
對於這種情況,可以將count變量聲明為原子變量,那么對於count的自增操作都可以以原子的方式進行,就不存在臟數據的讀取了;
原子變量類一共有12個,可以被分為4組:

常用的基於原子操作的方法:
1 //基於原子操作,獲取當前原子變量中的值並為其設置新值 2 public final int getAndSet(int newValue) 3 //基於原子操作,比較當前的value是否等於expect,如果是設置為update並返回true,否則返回false 4 public final boolean compareAndSet(int expect, int update) 5 //基於原子操作,獲取當前的value值並自增一 6 public final int getAndIncrement() 7 //基於原子操作,獲取當前的value值並自減一 8 public final int getAndDecrement() 9 //基於原子操作,獲取當前的value值並為value加上delta 10 public final int getAndAdd(int delta) 11 //還有一些反向的方法,比如:先自增在獲取值的等等
下例展示了使用AtomicInteger來實現int類型的原子性操作:
1 public class AtomicIntegerTest { 2 AtomicInteger count = new AtomicInteger(0); 3 void m() { 4 for (int i=0; i<10000; i++) { 5 count.incrementAndGet(); ////incrementAndGet()-先+1,再返回; getAndIncrement()-先返回,再+1 6 } 7 } 8 public static void main(String[] args) { 9 AtomicIntegerTest ait = new AtomicIntegerTest(); 10 List<Thread> threads = new ArrayList<>(); 11 for (int i=0; i<10; i++) { 12 threads.add(new Thread( ait::m , "thread" + i)); 13 } 14 threads.forEach((o)->o.start()); 15 threads.forEach((o)->{ 16 try { 17 o.join(); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 }); 22 System.out.println(ait.count); //100000 23 } 24 }
4. ReentrantLock-重入鎖
Java中實現鎖通常有兩種方式,一種是使用synchronized關鍵字,另一種是Lock:

4.1 ReentrantLock替代synchronized
1 public class ReentrantLockTest1 { 2 Lock lock = new ReentrantLock(); 3 void m1() { 4 try { 5 lock.lock(); //加鎖 //相當於synchronized(this) 6 for (int i=0; i<10; i++) { 7 TimeUnit.SECONDS.sleep(1); 8 System.out.print(" " + i); 9 } 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } finally { 13 lock.unlock(); //釋放鎖 14 } 15 } 16 void m2() { 17 lock.lock(); //加鎖 18 System.out.print(" m2()... "); 19 lock.unlock(); //釋放鎖 20 } 21 22 public static void main(String[] args) { 23 ReentrantLockTest1 r1 = new ReentrantLockTest1(); 24 new Thread(r1::m1).start(); 25 try { 26 TimeUnit.SECONDS.sleep(1); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 new Thread(r1::m2).start(); 31 } 32 }
注意:使用reentrantLock必須要手動釋放鎖,使用syn鎖定的話如果遇到異常,jvm會自動釋放鎖,但Lock必須手動釋放鎖,因此常在finally中進行鎖的釋放;
4.2 ReentrantLock可以進行嘗試鎖定tryLock()
1 public class ReentrantLock3 { 2 Lock lock = new ReentrantLock(); 3 void m1(){ 4 try{ 5 lock.lock(); 6 for(int i=0; i<10;i++){ 7 TimeUnit.SECONDS.sleep(1); 8 System.out.println(i); 9 } 10 }catch(InterruptedException e){ 11 e.printStackTrace(); 12 }finally { 13 lock.unlock(); 14 } 15 } 16 17 /** 18 * 使用trylock進行嘗試鎖定,不管鎖定與否,方法都將繼續執行 19 * 可以根據tryLock返回值來判斷是否鎖定 20 * 也可以指定tryLock的時間,由於tryLock(time)拋出異常,所以要注意tryLock的處理 21 * 必須放到finally中 22 */ 23 void m2(){ 24 /* 25 //第一種方式: 26 boolean locked = lock.tryLock(); 27 //不管拿沒拿到鎖都將執行 28 System.out.println("m2..."+locked); 29 if(locked){ 30 lock.unlock(); 31 } 32 */ 33 //第二種方式: 34 boolean locked = false; 35 try{ 36 //嘗試等5秒 37 locked = lock.tryLock(5,TimeUnit.SECONDS); 38 System.out.println("m2..."+locked); 39 }catch(InterruptedException e){ 40 e.printStackTrace(); 41 }finally{ 42 if(locked) 43 lock.unlock(); 44 } 45 } 46 47 public static void main(String[] args){ 48 ReentrantLock3 r3 = new ReentrantLock3(); 49 new Thread(r3::m1).start(); 50 try{ 51 TimeUnit.SECONDS.sleep(1); 52 }catch(InterruptedException e){ 53 e.printStackTrace(); 54 } 55 new Thread(r3::m2).start(); 56 } 57 }
4.3 使用ReentrantLock還可以調用lockInterruptibly()方法,可以對線程interrupt方法做出響應
在一個線程等待的過程中可以被打斷
1 public class ReentrantLock4 { 2 public static void main(String[] args){ 3 Lock lock = new ReentrantLock(); 4 Thread t1 = new Thread(()->{ 5 try{ 6 lock.lock(); 7 System.out.println("t1 start"); 8 TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); 9 System.out.println("t1 end"); 10 }catch(InterruptedException e){ 11 System.out.println("Interrupted!"); 12 }finally{ 13 lock.unlock(); 14 } 15 }); 16 t1.start(); 17 18 Thread t2 = new Thread(()->{ 19 boolean locked = false; 20 try { 21 lock.lockInterruptibly(); 22 System.out.println("t2 start"); 23 TimeUnit.SECONDS.sleep(5); 24 System.out.println("t2 end"); 25 }catch(InterruptedException e){ 26 System.out.println("interrupted"); 27 }finally{ 28 if(locked) 29 lock.unlock(); 30 } 31 }); 32 t2.start(); 33 try{ 34 TimeUnit.SECONDS.sleep(1); 35 }catch(InterruptedException e){ 36 e.printStackTrace(); 37 } 38 t2.interrupt(); 39 } 40 }
4.4. ReentrantLock指定為公平鎖(誰等的時間長,誰得到鎖)
1 public class ReentrantLock5 extends Thread{ 2 private static ReentrantLock lock = new ReentrantLock(true);//true表示公平鎖 3 public void run(){ 4 for(int i=0; i<100; i++){ 5 lock.lock(); 6 try{ 7 System.out.println(Thread.currentThread().getName()+"獲得鎖"); 8 9 }finally{ 10 lock.unlock(); 11 } 12 } 13 } 14 public static void main(String[] args){ 15 ReentrantLock5 r5 = new ReentrantLock5(); 16 Thread th1 = new Thread(r5); 17 Thread th2 = new Thread(r5); 18 th1.start(); 19 th2.start(); 20 } 21 }

4.5 面試題:寫一個固定容量的同步容器,有put和get方法,能夠支持2個生產者線程以及10個消費者線程的阻塞調用:
方法1:使用wait和notify/notifyAll來實現:
1 public class MyContainer1<T> { 2 final private LinkedList<T> lists = new LinkedList<>(); 3 final private int MAX =10; 4 private int count = 0; 5 6 public synchronized void put(T t){ 7 while(lists.size()==MAX){ 8 try{ 9 this.wait(); 10 }catch(InterruptedException e){ 11 e.printStackTrace(); 12 } 13 } 14 lists.add(t); 15 ++count; 16 this.notifyAll();//通知消費者線程進行消費 17 } 18 public synchronized T get(){ 19 T t = null; 20 //為什么用while? wait 99.9%情況下都是和while一起用的 21 // 22 while(lists.size()==0){ 23 try{ 24 this.wait(); 25 }catch(InterruptedException e){ 26 e.printStackTrace(); 27 } 28 } 29 t = lists.removeFirst(); 30 count--; 31 this.notifyAll(); 32 return t; 33 } 34 35 public static void main(String[] args){ 36 MyContainer1<String> c = new MyContainer1<>(); 37 //啟動消費者線程 38 for(int i=0; i<10; i++){ 39 new Thread(()->{ 40 for(int j=0; j<5; j++){ 41 System.out.println(c.get()); 42 } 43 },"c"+i).start(); 44 } 45 try{ 46 TimeUnit.SECONDS.sleep(2); 47 }catch(InterruptedException e){ 48 e.printStackTrace(); 49 } 50 51 //啟動生產者線程 52 for(int i=0;i<2;i++){ 53 new Thread(()->{ 54 for(int j =0;j<25;j++){ 55 c.put(Thread.currentThread().getName()+" "+j); 56 } 57 },"p"+i).start(); 58 } 59 } 60 }
方法2:使用
使用Lock和Condition的方式可以更加精確地指定哪些線程被喚醒
1 public class MyContainer2<T> { 2 final private LinkedList<T> lists= new LinkedList<>(); 3 final private int MAX =20; 4 private int count = 0; 5 6 private Lock lock = new ReentrantLock(); 7 private Condition producer = lock.newCondition(); 8 private Condition consumer = lock.newCondition(); 9 10 public void put(T t){ 11 try{ 12 lock.lock(); 13 while(lists.size()==MAX){ 14 producer.await(); 15 } 16 lists.add(t); 17 ++count; 18 consumer.signalAll(); 19 }catch(InterruptedException e){ 20 e.printStackTrace(); 21 }finally{ 22 lock.unlock(); 23 } 24 } 25 26 public T get(){ 27 T t = null; 28 try{ 29 lock.lock(); 30 while(lists.size()==0){ 31 consumer.await(); 32 } 33 t=lists.removeFirst(); 34 count--; 35 producer.signalAll(); 36 }catch(InterruptedException e){ 37 e.printStackTrace(); 38 }finally{ 39 lock.unlock(); 40 } 41 return t; 42 } 43 44 public static void main(String[] args){ 45 MyContainer2<String> c = new MyContainer2(); 46 47 for(int i=0; i<10; i++){ 48 new Thread(()->{ 49 for(int j=0; j<5; j++){ 50 System.out.println(c.get()); 51 } 52 },"c"+i).start(); 53 } 54 try{ 55 TimeUnit.SECONDS.sleep(2); 56 }catch (InterruptedException e){ 57 e.printStackTrace(); 58 } 59 60 //啟動生產者線程 61 for(int i=0;i<2;i++){ 62 new Thread(()->{ 63 for(int j =0;j<25;j++){ 64 c.put(Thread.currentThread().getName()+" "+j); 65 } 66 },"p"+i).start(); 67 } 68 } 69 }
5. 線程局部變量 ThreadLocal
1 public class ThreadLocal1 { 2 volatile static Person p = new Person(); 3 public static void main(String[] args){ 4 new Thread(()->{ 5 try{ 6 TimeUnit.SECONDS.sleep(2); 7 }catch(InterruptedException e){ 8 e.printStackTrace(); 9 } 10 System.out.println(p.name); 11 }).start(); 12 13 new Thread(()->{ 14 try{ 15 TimeUnit.SECONDS.sleep(1); 16 }catch(InterruptedException e){ 17 e.printStackTrace(); 18 } 19 p.name = "lisi"; 20 }).start(); 21 } 22 } 23 class Person{ 24 String name = "zhangsan"; 25 }
結果為”lisi“
設定為線程局部變量:
1 public class ThreadLocal2 { 2 static ThreadLocal<Person> tl = new ThreadLocal<>(); 3 public static void main(String[] args){ 4 new Thread(()->{ 5 try{ 6 TimeUnit.SECONDS.sleep(2); 7 }catch(InterruptedException e){ 8 e.printStackTrace(); 9 } 10 System.out.println(tl.get()); 11 }).start(); 12 13 new Thread(()->{ 14 try { 15 TimeUnit.SECONDS.sleep(1); 16 }catch(InterruptedException e){ 17 e.printStackTrace(); 18 } 19 }).start(); 20 } 21 static class Person{ 22 String name = "zhangsan"; 23 } 24 }
結果為null。
