JUC : 並發編程工具類的使用


個人博客網:https://wushaopei.github.io/    (你想要這里多有)

一、JUC是什么

1、JUC定義

JUC,即java.util.concurrent 在並發編程中使用的工具類

              

2、進程、線程的定義

2.1 進程、線程是什么?

進程:進程是一個具有一定獨立功能的程序關於某個數據集合的一次運行活動。它是操作系統動態執行的基本單元,在傳統的操作系統中,進程既是基本的分配單元,也是基本的執行單元。
 
線程:通常在一個進程中可以包含若干個線程,當然一個進程中至少有一個線程,不然沒有存在的意義。線程可以利用進程所擁有的資源,在引入線程的操作系統中,通常都是把進程作為分配資源的基本單位,而把線程作為獨立運行和獨立調度的基本單位,由於線程比進程更小,基本上不擁有系統資源,故對它的調度所付出的開銷就會小得多,能更高效的提高系統多個程序間並發執行的程度。

2.2 進程、線程例子

  •  使用QQ,查看進程一定有一個QQ.exe的進程,我可以用qq和A文字聊天,和B視頻聊天,給C傳文件,給D發一段語言,QQ支持錄入信息的搜索。
  • 大四的時候寫論文,用word寫論文,同時用QQ音樂放音樂,同時用QQ聊天,多個進程。
  • word如沒有保存,停電關機,再通電后打開word可以恢復之前未保存的文檔,word也會檢查你的拼寫,兩個線程:容災備份,語法檢查

二、Lock 接口

1、Lock 接口定義

Lock :Lock 是juc 下的一個接口類,用於對多線程的同步實現。

            

2、Lock接口的實現ReentrantLock可重入鎖

         

2.1 Lock 的使用示例:

class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } } }

3、Thread 類 創建線程的方式

3.1 繼承Thread 類

public class SaleTicket extends Thread

問題:java是單繼承,資源寶貴,要用接口方式

如果使用繼承Thread 類的方式來創建線程,對資源是很大的浪費。所以實際開發中不建議這么寫

3.2  new Thread()

Thread t1 = new Thread(); t1.start();

這里通過創建線程實例的方式來創建線程,但在實際開發中,這樣是不好的,對資源的浪費依舊居高不下。

3.3 通過接口實現類為形參的方式注入創建 Thread 線程

       

Thread(Runnable target, String name) 

  這里是通過 Tread 有參構造的方式創建一個新的線程對象。

4、實現現成的三種方法

4.1 新建類實現 runnable接口

class MyThread implements Runnable//新建類實現runnable接口 new Thread(new MyThread,...)

這種方法會新增類,有更新更好的方法

4.2 匿名內部類

   new Thread(new Runnable() { @Override public void run() { } }, "your thread name").start();

這種方法不需要創建新的類,可以 new 接口

4.3 lambda 表達式

 new Thread(() -> { }, "your thread name").start();

  這種方法代碼更簡潔精煉

5、案例代碼:

package com.atguigu.thread;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
class Ticket //實例例eld +method
{
 private int number=30;
/* //1同步 public synchronized void sale() 
 {//2同步  synchronized(this) {}
  if(number > 0) {
    System.out.println(Thread.currentThread().getName()+"賣出"+(number--)+"\t 還剩number);
   }
 }*/
 
// Lock implementations provide more extensive locking operations
// than can be obtained using synchronized methods and statements. 
 private Lock lock = new ReentrantLock();//List list = new ArrayList()
 
 public void sale() {
   lock.lock();
   
   try {
    if(number > 0) {
     System.out.println(Thread.currentThread().getName()+"賣出"+(number--)+"\t 還剩number);
    }
   } catch (Exception e) {
    e.printStackTrace();
   } finally {
    lock.unlock();
   }
 }
}
 
/**
 * 
 * @Description:賣票程序個售票出  0張票
 @author xiale
 * 筆記:J里面如何 1 多線程編-上
    1.1 線程  (資里源類 *   1.2 高內聚 /
public class SaleTicket 
{
 public static void main(String[] args)//main所有程序
   Ticket ticket = new Ticket();
   //Thread(Runnable target, String name) Allocates a new Thread object.
 
 new Thread(() -> {for (int i = 1; i < 40; i++)ticket.sale();}, "AA").start();
 new Thread(() -> {for (int i = 1; i < 40; i++)ticket.sale();}, "BB").start();
 new Thread(() -> {for (int i = 1; i < 40; i++)ticket.sale();}, "CC").start();

/*  new Thread(new Runnable() {
    @Override
    public void run() 
    {
     for (int i = 1; i <=40; i++) 
     {
       
       ticket.sale();
     }
    }
   }, "AA").start();
   
   new Thread(new Runnable() {
    @Override
    public void run() 
    {
     for (int i = 1; i <=40; i++) 
     {
       ticket.sale();
     }
    }
   }, "BB").start();
   new Thread(new Runnable() {
    @Override
    public void run() 
    {
     for (int i = 1; i <=40; i++) 
     {
       ticket.sale();
     }
    }
   }, "CC").start();
   */
 }
}
//1 class MyThread implements Runnable
 
//2 匿名內部類
 
//3 laExpress

三、java 8 新特性

1、lambda 表達式

1.1 查看例子: LambdaDemo

1.2 要求:接口只有一個方法

lambda表達式,如果一個接口只有一個方法,我可以把方法名省略
Foo foo = () -> {System.out.println("****hello lambda");};

寫法分析: 拷貝小括號(),寫死右箭頭->,落地大括號{...}

1.3 函數式接口

lambda表達式,必須是函數式接口,必須只有一個方法;如果接口只有一個方法java默認它為函數式接口。

為了正確使用Lambda表達式,需要給接口加個注解:

@FunctionalInterface

如有兩個方法,立刻報錯


          Runnable接口為什么可以用lambda表達式?

         


2、接口里是否能有實現方法?

2.1 default 方法

接口里在java8后容許有接口的實現,default方法默認實現

default int div(int x,int y) { return x/y; }

     接口里default方法可以有幾個?

2.2 靜態方法實現

靜態方法實現:接口新增

public static int sub(int x,int y){ return x-y; }

可以有幾個?
注意靜態的叫類方法,能用foo去調嗎?要改成Foo

3、代碼

package com.atguigu.thread; @FunctionalInterface interface Foo{ // public void sayHello() ; // public void say886() ; public int add(int x,int y); default int div(int x,int y) { return x/y; } public static int sub(int x,int y) { return x-y; } } /** * * @Description: Lambda Express-----> 函數式編程 * 1 拷貝小括號(形參列表),寫死右箭頭 ->,落地大括號 {方法實現} * 2 有且只有一個public方法@FunctionalInterface注解增強定義 * 3 default方法默認實現 * 4 靜態方法實現 */ public class LambdaDemo { public static void main(String[] args) { // Foo foo = new Foo() { // @Override // public void sayHello() { // System.out.println("Hello!!"); // } // // @Override // public void say886() { // // TODO Auto-generated method stub // // } // }; // foo.sayHello(); // System.out.println("============"); // foo = ()->{System.out.println("Hello!! lambda !!");}; // foo.sayHello(); Foo foo = (x,y)->{ System.out.println("Hello!! lambda !!"); return x+y; }; int result = foo.add(3,5); System.out.println("******result="+result); System.out.println("******result div="+foo.div(10, 2)); System.out.println("******result sub="+Foo.sub(10, 2)); } }

四、Callalbe 接口

1、Callable接口

1.1 是什么?

定義:Callable 是用來獲得多線程的方法。


注意:(1)繼承thread類(2)runnable接口
如果只回答這兩個你連被問到juc的機會都沒有


2、與Runnable 對比

創建新類MyThread實現runnable接口

class MyThread implements Runnable{ @Override public void run() { } }

新類MyThread2實現callable接口

class MyThread2 implements Callable<Integer>{ @Override public Integer call() throws Exception { return 200; } }

 面試題: callable接口與runnable接口的區別?

答:(1)是否有返回值
       (2)是否拋異常
       (3)落地方法不一樣,一個是run,一個是call

3、Callable 怎么用?

不能直接替換Runnable 來使用,原因: thread類的構造方法根本沒有Callable

     

解決辦法:java 多態,一個類可以實現多個接口

   

從上圖可以知道,Runnable 實現類FutureTask 接口;Callable 的實例可以作為Future Task 的構造參數。

FutureTask<Integer> ft = new FutureTask<Integer>(new MyThread()); new Thread(ft, "AA").start();

如以上的函數一樣,實現多線程的創建。

關於 運行成功后如何獲得返回值?

 

ft.get();

4、Future Task

4.1 Future Task 是什么?

                            

在項目中,用它在執行線程時,做異步調用,類似於使用 main 將一個個方法串起來,從而解決: 正常調用掛起堵塞問題。

4.2 原理

在主線程中需要執行比較耗時的操作時,但又不想阻塞主線程時,可以把這些作業交給Future對象在后台完成,當主線程將來需要時,就可以通過Future對象獲得后台作業的計算結果或者執行狀態。

一般FutureTask多用於耗時的計算,主線程可以在完成自己的任務后,再去獲取結果。

僅在計算完成時才能檢索結果;如果計算尚未完成,則阻塞 get 方法。一旦計算完成,就不能再重新開始或取消計算。get方法而獲取結果只有在計算完成時獲取,否則會一直阻塞直到任務轉入完成狀態,然后會返回結果或者拋出異常。
 

  • 只計算一次
  • get方法放到最后

4.3 代碼

package com.atguigu.thread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; class MyThread implements Callable<Integer> { @Override public Integer call() throws Exception { Thread.sleep(4000); System.out.println(Thread.currentThread().getName()+" *****come in call"); return 200; } } /** * * @Description: Callable接口獲得多線程 * @author xialei * @date * 筆記結論見最后 */ public class CallableDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { FutureTask<Integer> ft = new FutureTask<Integer>(new MyThread()); new Thread(ft, "AA").start(); /*FutureTask<Integer> ft2 = new FutureTask<Integer>(new MyThread()); new Thread(ft2, "BB").start();*/ System.out.println(Thread.currentThread().getName()+"------main"); Integer result = ft.get(); //Integer result2 = ft2.get(); System.out.println("**********result: "+result); } } /** * * 在主線程中需要執行比較耗時的操作時,但又不想阻塞主線程時,可以把這些作業交給Future對象在后台完成, 當主線程將來需要時,就可以通過Future對象獲得后台作業的計算結果或者執行狀態。 一般FutureTask多用於耗時的計算,主線程可以在完成自己的任務后,再去獲取結果。 僅在計算完成時才能檢索結果;如果計算尚未完成,則阻塞 get 方法。一旦計算完成, 就不能再重新開始或取消計算。get方法而獲取結果只有在計算完成時獲取,否則會一直阻塞直到任務轉入完成狀態, 然后會返回結果或者拋出異常。 只計算一次 get方法放到最后 */

五、線程間通信

1、面試題:兩個線程打印

兩個線程,一個線程打印1-52,另一個打印字母A-Z打印順序為12A34B...5152Z,
要求用線程間通信

2、例子:NotifyWaitDemo

3、線程間通信:1、生產者+消費者2、通知等待喚醒機制

4、多線程編程模板下

  • 判斷
  • 干活
  • 通知

5、synchronized實現

5.1 代碼

package com.atguigu.thread; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.omg.IOP.Codec; class ShareDataOne//資源類 { private int number = 0;//初始值為零的一個變量 public synchronized void increment() throws InterruptedException { //1判斷 if(number !=0 ) { this.wait(); } //2干活 ++number; System.out.println(Thread.currentThread().getName()+"\t"+number); //3通知 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { // 1判斷 if (number == 0) { this.wait(); } // 2干活 --number; System.out.println(Thread.currentThread().getName() + "\t" + number); // 3通知 this.notifyAll(); } } /** * * @Description: *現在兩個線程, * 可以操作初始值為零的一個變量, * 實現一個線程對該變量加1,一個線程對該變量減1, * 交替,來10輪。 * @author xialei * * * 筆記:Java里面如何進行工程級別的多線程編寫 * 1 多線程變成模板(套路)-----上 * 1.1 線程 操作 資源類 * 1.2 高內聚 低耦合 * 2 多線程變成模板(套路)-----下 * 2.1 判斷 * 2.2 干活 * 2.3 通知 */ public class NotifyWaitDemoOne { public static void main(String[] args) { ShareDataOne sd = new ShareDataOne(); new Thread(() -> { for (int i = 1; i < 10; i++) { try { sd.increment(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 1; i < 10; i++) { try { sd.decrement(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }, "B").start(); } }

5.2 如果換成4個線程執行呢?

如果換成4個線程會導致錯誤,虛假喚醒

  • 原因:在java多線程判斷時,不能用if,程序出事出在了判斷上面
  • 突然有一天加的線程進到if了,突然中斷了交出控制權
  • 沒有進行驗證,而是直接走下去了,加了兩次,甚至多次

5.3 解決辦法

解決虛假喚醒:查看API,java.lang.Object

    

中斷和虛假喚醒是可能產生的,所以要用loop循環,if只判斷一次,while是只要喚醒就要拉回來再判斷一次。if換成while

(1)代碼

package com.atguigu.thread; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.omg.IOP.Codec; /* * * * 2 多線程變成模板(套路)-----下 * 2.1 判斷 * 2.2 干活 * 2.3 通知 * 3 防止虛假喚醒用while * */ class ShareData//資源類 { private int number = 0;//初始值為零的一個變量 public synchronized void increment() throws InterruptedException { //判斷 while(number!=0) { this.wait(); } //干活 ++number; System.out.println(Thread.currentThread().getName()+" \t "+number); //通知 this.notifyAll();; } public synchronized void decrement() throws InterruptedException { //判斷 while(number!=1) { this.wait(); } //干活 --number; System.out.println(Thread.currentThread().getName()+" \t "+number); //通知 this.notifyAll(); } } /** * * @Description: *現在兩個線程, * 可以操作初始值為零的一個變量, * 實現一個線程對該變量加1,一個線程對該變量減1, * 交替,來10輪。 * * * 筆記:Java里面如何進行工程級別的多線程編寫 * 1 多線程變成模板(套路)-----上 * 1.1 線程 操作 資源類 * 1.2 高內聚 低耦合 * 2 多線程變成模板(套路)-----下 * 2.1 判斷 * 2.2 干活 * 2.3 通知 */ public class NotifyWaitDemo { public static void main(String[] args) { ShareData sd = new ShareData(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { sd.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { sd.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { sd.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { sd.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start(); } } 

(2)原理圖:

                         

6、java8新版實現

6.1 對標實現

              

6.2 Condition

        

class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } }

代碼:

package com.atguigu.thread; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.omg.IOP.Codec; class ShareData{//資源類 private int number = 0;//初始值為零的一個變量 private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void increment() throws InterruptedException { lock.lock(); try { //判斷 while(number!=0) { condition.await(); } //干活 ++number; System.out.println(Thread.currentThread().getName()+" \t "+number); //通知 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrement() throws InterruptedException{ lock.lock(); try { //判斷 while(number!=1) { condition.await(); } //干活 --number; System.out.println(Thread.currentThread().getName()+" \t "+number); //通知 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } /*public synchronized void increment() throws InterruptedException{ //判斷 while(number!=0) { this.wait(); } //干活 ++number; System.out.println(Thread.currentThread().getName()+" \t "+number); //通知 this.notifyAll();; } public synchronized void decrement() throws InterruptedException { //判斷 while(number!=1) { this.wait(); } //干活 --number; System.out.println(Thread.currentThread().getName()+" \t "+number); //通知 this.notifyAll(); }*/ } /** * * @Description: *現在兩個線程, * 可以操作初始值為零的一個變量, * 實現一個線程對該變量加1,一個線程對該變量減1, * 交替,來10輪。 * @author xialei * * * 筆記:Java里面如何進行工程級別的多線程編寫 * 1 多線程變成模板(套路)-----上 * 1.1 線程 操作 資源類 * 1.2 高內聚 低耦合 * 2 多線程變成模板(套路)-----下 * 2.1 判斷 * 2.2 干活 * 2.3 通知 */ public class NotifyWaitDemo{ public static void main(String[] args){ ShareData sd = new ShareData(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { sd.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { sd.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { sd.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { try { sd.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start(); } }

六、線程間定制化調用通信

1、例子:ThreadOrderAccess

2、線程-調用-資源類

3、判斷-干活-通知

  1. 有順序通知,需要有標識位
  2. 有一個鎖Lock,3把鑰匙Condition
  3. 判斷標志位
  4. 輸出線程名+第幾次+第幾輪
  5. 修改標志位,通知下一個

4、代碼

package com.atguigu.thread; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class ShareResource { private int number = 1;//1:A 2:B 3:C private Lock lock = new ReentrantLock(); private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); private Condition c3 = lock.newCondition(); public void print5(int totalLoopNumber) { lock.lock(); try { //1 判斷 while(number != 1){ //A 就要停止 c1.await(); } //2 干活 for (int i = 1; i <=5; i++) { System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber); } //3 通知 number = 2; c2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print10(int totalLoopNumber) { lock.lock(); try{ //1 判斷 while(number != 2) { //A 就要停止 c2.await(); } //2 干活 for (int i = 1; i <=10; i++) { System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber); } //3 通知 number = 3; c3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print15(int totalLoopNumber) { lock.lock(); try { //1 判斷 while(number != 3){ //A 就要停止 c3.await(); } //2 干活 for (int i = 1; i <=15; i++) { System.out.println(Thread.currentThread().getName()+"\t"+i+"\t totalLoopNumber: "+totalLoopNumber); } //3 通知 number = 1; c1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } /** * * @Description: * 多線程之間按順序調用,實現A->B->C * 三個線程啟動,要求如下: * * AA打印5次,BB打印10次,CC打印15次 * 接着 * AA打印5次,BB打印10次,CC打印15次 * ......來10輪 * @author xialei * */ public class ThreadOrderAccess { public static void main(String[] args) { ShareResource sr = new ShareResource(); new Thread(() -> { for (int i = 1; i <=10; i++) { sr.print5(i); } }, "AA").start(); new Thread(() -> { for (int i = 1; i <=10; i++) { sr.print10(i); } }, "BB").start(); new Thread(() -> { for (int i = 1; i <=10; i++) { sr.print15(i); } }, "CC").start(); } }

七、多線程鎖

1、例子:ThreadOrderAccess

2、鎖的 8 個問題

  1. 標准訪問,先打印短信還是郵件
  2. 停4秒在短信方法內,先打印短信還是郵件
  3. 普通的hello方法,是先打短信還是hello
  4. 現在有兩部手機,先打印短信還是郵件
  5. 兩個靜態同步方法,1部手機,先打印短信還是郵件
  6. 兩個靜態同步方法,2部手機,先打印短信還是郵件
  7. 1個靜態同步方法,1個普通同步方法,1部手機,先打印短信還是郵件
  8. 1個靜態同步方法,1個普通同步方法,2部手機,先打印短信還是郵件

 3、8 鎖分析

A 一個對象里面如果有多個synchronized方法,某一個時刻內,只要一個線程去調用其中的一個synchronized方法了,
其它的線程都只能等待,換句話說,某一個時刻內,只能有唯一一個線程去訪問這些synchronized方法
鎖的是當前對象this,被鎖定后,其它的線程都不能進入到當前對象的其它的synchronized方法

  • 加個普通方法后發現和同步鎖無關
  • 換成兩個對象后,不是同一把鎖了,情況立刻變化。 

synchronized實現同步的基礎Java中的每一個對象都可以作為鎖。
具體表現為以下3種形式:

  1. 對於普通同步方法,鎖是當前實例對象。
  2. 對於靜態同步方法,鎖是當前類的Class對象。
  3. 對於同步方法塊,鎖是Synchonized括號里配置的對象
當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。

也就是說如果一個實例對象的非靜態同步方法獲取鎖后,該實例對象的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖后才能獲取鎖,可是別的實例對象的非靜態同步方法因為跟該實例對象的非靜態同步方法用的是不同的鎖,所以毋須等待該實例對象已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖。

所有的靜態同步方法用的也是同一把鎖——類對象本身,這兩把鎖是兩個不同的對象,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的。

但是一旦一個靜態同步方法獲取鎖后,其他的靜態同步方法都必須等待該方法釋放鎖后才能獲取鎖,而不管是同一個實例對象的靜態同步方法之間,還是不同的實例對象的靜態同步方法之間,只要它們同一個類的實例對象!

4、代碼

package com.atguigu.thread; import java.util.concurrent.TimeUnit; class Phone{ public synchronized void sendSMS() throws Exception { System.out.println("------sendSMS"); } public synchronized void sendEmail() throws Exception { System.out.println("------sendEmail"); } public void getHello() { System.out.println("------getHello"); } } /** * @Description: 8鎖 * @author xialei * 1 標准訪問,先打印短信還是郵件 2 停4秒在短信方法內,先打印短信還是郵件 3 新增普通的hello方法,是先打短信還是hello 4 現在有兩部手機,先打印短信還是郵件 5 兩個靜態同步方法,1部手機,先打印短信還是郵件 6 兩個靜態同步方法,2部手機,先打印短信還是郵件 7 1個靜態同步方法,1個普通同步方法,1部手機,先打印短信還是郵件 8 1個靜態同步方法,1個普通同步方法,2部手機,先打印短信還是郵件 * --------------------------------- */ public class Lock_8 { public static void main(String[] args) throws Exception { Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { try { phone.sendSMS(); } catch (Exception e) { e.printStackTrace(); } }, "AA").start(); Thread.sleep(100); new Thread(() -> { try { phone.sendEmail(); //phone.getHello(); //phone2.sendEmail(); } catch (Exception e) { e.printStackTrace(); } }, "BB").start(); } } 

八、JUC強大的輔助類講解

1、ReentrantReadWriteLock 讀寫鎖

該類用於解決 寫寫、讀寫互斥的場景下,當進行寫操作時,不進行讀操作和其他的寫操作;進行讀操作時不進行寫操作;確保數據 的一致性和梯次讀取到數據的一致性。

(1)例子:ReadWriteLockDemo

(2)類似軟件: 紅蜘蛛

(3)代碼:

package com.atguigu.thread; import java.util.concurrent.locks.ReentrantReadWriteLock; class MyQueue{ private Object obj; private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); public void readObj(){ rwLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName()+"\t"+obj); } catch (Exception e) { e.printStackTrace(); } finally { rwLock.readLock().unlock(); } } public void writeObj(Object obj){ rwLock.writeLock().lock(); try { this.obj = obj; System.out.println(Thread.currentThread().getName()+"writeThread:\t"+obj); } catch (Exception e) { e.printStackTrace(); } finally { rwLock.writeLock().unlock(); } } } /** * * @Description: 一個線程寫入,100個線程讀取 * @author xialei * */ public class ReadWriteLockDemo{ public static void main(String[] args) throws InterruptedException { MyQueue q = new MyQueue(); new Thread(() -> { q.writeObj("ClassName1221"); }, "AAAAA").start(); for (int i = 1; i <=100; i++) { new Thread(() -> { q.readObj(); },String.valueOf(i)).start(); } } }

2、CountDownLatch

(1)例子:CountDownLatchDemo

(2)原理:

  •  CountDownLatch主要有兩個方法,當一個或多個線程調用await方法時,這些線程會阻塞。
  •  其它線程調用countDown方法會將計數器減1(調用countDown方法的線程不會阻塞),
  •  當計數器的值變為0時,因await方法阻塞的線程會被喚醒,繼續執行。

(3)代碼:

package com.atguigu.thread; import java.util.concurrent.CountDownLatch; /** * @Description: * *讓一些線程阻塞直到另一些線程完成一系列操作后才被喚醒。 * * CountDownLatch主要有兩個方法,當一個或多個線程調用await方法時,這些線程會阻塞。 * 其它線程調用countDown方法會將計數器減1(調用countDown方法的線程不會阻塞), * 當計數器的值變為0時,因await方法阻塞的線程會被喚醒,繼續執行。 * * 解釋:6個同學陸續離開教室后值班同學才可以關門。 * * main主線程必須要等前面6個線程完成全部工作后,自己才能開干 */ public class CountDownLatchDemo{ public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 1; i <=6; i++){ //6個上自習的同學,各自離開教室的時間不一致 new Thread(() -> { System.out.println(Thread.currentThread().getName()+"\t 號同學離開教室"); countDownLatch.countDown(); }, String.valueOf(i)).start(); } countDownLatch.await(); System.out.println(Thread.currentThread().getName()+"\t****** 班長關門走人,main線程是班長"); } }

3、CyclicBarrier 循環柵欄

(1)例子: CountDownLatchDemo

(2)原理:

  •  CyclicBarrier
  •  的字面意思是可循環(Cyclic)使用的屏障(Barrier)。它要做的事情是,
  •  讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,
  •  直到最后一個線程到達屏障時,屏障才會開門,所有
  •  被屏障攔截的線程才會繼續干活。
  •  線程進入屏障通過CyclicBarrier的await()方法。

(3)代碼

package com.atguigu.thread; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** * * @Description: TODO(這里用一句話描述這個類的作用) * * CyclicBarrier * 的字面意思是可循環(Cyclic)使用的屏障(Barrier)。它要做的事情是, * 讓一組線程到達一個屏障(也可以叫同步點)時被阻塞, * 直到最后一個線程到達屏障時,屏障才會開門,所有 * 被屏障攔截的線程才會繼續干活。 * 線程進入屏障通過CyclicBarrier的await()方法。 * * 集齊7顆龍珠就可以召喚神龍 */ public class CyclicBarrierDemo { private static final int NUMBER = 7; public static void main(String[] args) { //CyclicBarrier(int parties, Runnable barrierAction) CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, ()->{System.out.println("*****集齊7顆龍珠就可以召喚神龍");}) ; for (int i = 1; i <= 7; i++) { new Thread(() -> { try { System.out.println(Thread.currentThread().getName()+"\t 星龍珠被收集 "); cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } }, String.valueOf(i)).start(); } } }

4、Semaphore 信號燈

(1)例子:CountDownLatchDemo

(2)原理:

在信號量上我們定義兩種操作:

  •  acquire(獲取) 當一個線程調用acquire操作時,它要么通過成功獲取信號量(信號量減1),
  •  要么一直等下去,直到有線程釋放信號量,或超時。
  •  release(釋放)實際上會將信號量的值加1,然后喚醒等待的線程。
  •  信號量主要用於兩個目的,一個是用於多個共享資源的互斥使用,另一個用於並發線程數的控制。

(3)代碼

package com.atguigu.thread; import java.util.Random; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** * * @Description: TODO(這里用一句話描述這個類的作用) * @author xialei * * 在信號量上我們定義兩種操作: * acquire(獲取) 當一個線程調用acquire操作時,它要么通過成功獲取信號量(信號量減1), * 要么一直等下去,直到有線程釋放信號量,或超時。 * release(釋放)實際上會將信號量的值加1,然后喚醒等待的線程。 * * 信號量主要用於兩個目的,一個是用於多個共享資源的互斥使用,另一個用於並發線程數的控制。 */ public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore = new Semaphore(3);//模擬3個停車位 for (int i = 1; i <=6; i++) { //模擬6部汽車 new Thread(() -> { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"\t 搶到了車位"); TimeUnit.SECONDS.sleep(new Random().nextInt(5)); System.out.println(Thread.currentThread().getName()+"\t------- 離開"); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release(); } }, String.valueOf(i)).start(); } } } 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM