天天看點

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 線程

JUC : 并發程式設計工具類的使用

​​

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

JUC : 并發程式設計工具類的使用

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

JUC : 并發程式設計工具類的使用

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

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

如以上的函數一樣,實作多線程的建立。

關于 運作成功後如何獲得傳回值?

JUC : 并發程式設計工具類的使用
ft.get();           

4、Future Task

4.1 Future Task 是什麼?

JUC : 并發程式設計工具類的使用

在項目中,用它在執行線程時,做異步調用,類似于使用 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

JUC : 并發程式設計工具類的使用

中斷和虛假喚醒是可能産生的,是以要用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)原理圖:

JUC : 并發程式設計工具類的使用

6、java8新版實作

6.1 對标實作

JUC : 并發程式設計工具類的使用

6.2 Condition

JUC : 并發程式設計工具類的使用
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();   
  }
}           

七、多線程鎖

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括号裡配置的對象
當一個線程試圖通路同步代碼塊時,它首先必須得到鎖,退出或抛出異常時必須釋放鎖。           

也就是說如果一個執行個體對象的非靜态同步方法擷取鎖後,該執行個體對象的其他非靜态同步方法必須等待擷取鎖的方法釋放鎖後才能擷取鎖,可是别的執行個體對象的非靜态同步方法因為跟該執行個體對象的非靜态同步方法用的是不同的鎖,是以毋須等待該執行個體對象已擷取鎖的非靜态同步方法釋放鎖就可以擷取他們自己的鎖。

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

但是一旦一個靜态同步方法擷取鎖後,其他的靜态同步方法都必須等待該方法釋放鎖後才能擷取鎖,而不管是同一個執行個體對象的靜态同步方法之間,還是不同的執行個體對象的靜态同步方法之間,隻要它們同一個類的執行個體對象!

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方法阻塞的線程會被喚醒,繼續執行。
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

  •  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 信号燈

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

  •  acquire(擷取) 當一個線程調用acquire操作時,它要麼通過成功擷取信号量(信号量減1),
  •  要麼一直等下去,直到有線程釋放信号量,或逾時。
  •  release(釋放)實際上會将信号量的值加1,然後喚醒等待的線程。
  •  信号量主要用于兩個目的,一個是用于多個共享資源的互斥使用,另一個用于并發線程數的控制。
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();
     }     
  }
}
           

繼續閱讀