1. 程式人生 > >java中執行緒安全,執行緒死鎖,執行緒通訊快速入門

java中執行緒安全,執行緒死鎖,執行緒通訊快速入門

一:多執行緒安全問題

###1 引入

    /*
     * 多執行緒併發訪問同一個資料資源
     * 3個執行緒,對一個票資源,出售
     */
    public class ThreadDemo {
     public static void main(String[] args) {
       //建立Runnable介面實現類物件
       Tickets t = new Tickets();
       //建立3個Thread類物件,傳遞Runnable介面實現類
       Thread t0 = new Thread(t);
       Thread t1 = new Thread(t);
       Thread t2 = new Thread(t);
       
       t0.start();
       t1.start();
       t2.start();
       
     }
    }

/* * 通過執行緒休眠,出現安全問題 */ public class Tickets implements Runnable{ //定義出售的票源 private int ticket = 100; private Object obj = new Object(); public void run(){ while(true){ //對票數判斷,大於0,可以出售,變數--操作 if( ticket > 0){ try{ Thread.sleep(50); //加了休眠讓其他執行緒有執行機會 }catch(Exception ex){} System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--); } } } }

 

執行結果出現了這麼一種情況:

可見票數為0和-1時都進行了售賣,由此可見多執行緒操作共享資料存在安全隱患

具體的講:該處有三個執行緒t0,t1,t2同時對tickets進行操作,程式一執行,3個執行緒搶佔CPU資源,執行執行過if(tickets>0)接著執行休眠操作,在這短短50ms的夠CPU幹很多事了,繼續賣票。到最後休眠時間結束,執行緒無需在進行判斷tickets是否大於0,便接著往下執行,就導致了安全問題

 ###2:解決辦法

###2.1java中提供了同步機制,能夠解決執行緒的安全性問題。

    //同步程式碼塊,  同步程式碼塊的鎖物件可以是任意的物件
         synchronized (鎖物件){
            可能產生執行緒安全問題的程式碼
            }
          

    //同步方法, 同步方法中的鎖物件是 this
        public synchronized void method()
              可能產生執行緒安全問題的程式碼
        }
           
    //靜態同步方法,靜態同步方法中的鎖物件是 類名.class
        public synchronized void method()
                      可能產生執行緒安全問題的程式碼
        }
     

###2.2同步方法(推薦)或同步程式碼塊解決該售票例子的執行緒安全問題

public class Tickets implements Runnable {

    // 共一百票
    int tickets = 20;
    Object obj = new Object();

    @Override
    public void run() {
        // 模擬賣票
        while (true) {
            method();
        }
    }

    public synchronized void method() {
        if (tickets > 0) {
            // 執行緒休眠模擬安全問題
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在賣票:" + tickets--);
        }
    }

}

###2.3同步程式碼塊的原理

同步操作給物件上了一把物件鎖(物件監視器),沒有鎖的執行緒不能夠繼續往下執行,只能等。
執行緒遇到同步程式碼塊後,執行緒判斷是否有同步鎖,有則獲取鎖,進入同步中去執行,執行完畢釋放鎖。沒有則不能夠進行同步程式碼塊中
由於加了同步後,執行緒進同步判斷鎖,獲取鎖,執行完畢釋放鎖,導致程式的執行速度下降。

舉個上廁所的例子:假設有一片區域只有一個廁所且只有一個坑位(共享資料),有三人A,B,C(三執行緒)需要上廁所,A拿著鑰匙先進去上小廁,需要開門,這個門就相當於物件鎖,你進來就得先開門並關上,小廁上了一分鐘(Thread.sleep),上完出來給鑰匙給B(釋放鎖),B在拿著鑰匙去開門上大廁,時間十分鐘。。這時候C就只能在門外乾急著了等B上完了

##3:Lock鎖對synchronized的改進

使用同步方法有個缺點:當在sleep休眠時若發生了異常,則該執行緒是出不了同步的,鎖物件釋放不了。

因此,SUN公司在jdk5後提供了個Lock介面,Lock介面中的常用方法

void lock()
void unlock()

public class Tickets implements Runnable {

    // 共一百票
    int tickets = 20;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        // 模擬賣票
        while (true) {
            //呼叫lock方法加鎖
            lock.lock();
            if (tickets > 0) {
                // 執行緒休眠模擬安全問題
                try {
                    Thread.sleep(50);
                    System.out.println(Thread.currentThread().getName()+" 出售第"+tickets--);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

}

 

二:執行緒的死鎖

 同步鎖使用的弊端:當執行緒任務中出現了多個同步(多個鎖)時,如果同步中嵌套了其他的同步。這時容易引發一種現象:程式出現無限等待,這種現象我們稱為死鎖。這種情況能避免就避免掉

synchronzied(A鎖){
    synchronized(B鎖){
         
  }
}

 死鎖的一個形象比喻:兩個人打架互相揪著對方頭髮不放,A說你先放,B說你先放,兩人都不肯先放,就造成了死鎖.

 下面為產生死鎖的一個例子

 

public class lockA {
    //保證物件的唯一性
    private lockA(){
        
    }
    public final static lockA locka = new lockA();
}


public class LockB {
    //保證物件的唯一性
    private LockB() {
        
    }
    public static final LockB lockb = new LockB();
}


public class DeadLock implements Runnable {

    private int i = 0;

    @Override
    public void run() {
        while (true) {
            if (i % 2 == 0) {
                // 先進入A同步,在進入B同步
                synchronized (lockA.locka) {
                    System.out.println("if---locka");
                    synchronized(LockB.lockb){
                        System.out.println("if---lockb");
                    }
                }
            } else {
                //先進入B同步在進入A同步
                synchronized (LockB.lockb) {
                    System.out.println("else---lockb");
                    synchronized(lockA.locka){
                        System.out.println("else---locka");
                    }
                }
            }
            i++;
        }

    }

}


public class DeadLockDemo {
    public static void main(String[] args) {
        DeadLock deadLock = new DeadLock();
        Thread t0 = new Thread(deadLock);
        Thread t1 = new Thread(deadLock);
        t0.start();t1.start();
    }
}

 

 

 三:執行緒通訊

執行緒之間的通訊:多個執行緒在處理同一個資源,但是處理的動作(執行緒的任務)卻不相同。通過一定的手段使各個執行緒能有效的利用資源。而這種手段即—— 等待喚醒機制

打個比喻:就好像平時收快遞一樣,快遞先由賣家包裝,聯絡收件人員收發貨,快遞一路經過各個地點中轉在到達你的手裡。把這一系列過程看作一個個執行緒,所有執行緒共同合作處理你這個包裹,從而達到有效利用資源。

Java實現程式碼如下:

A 執行緒等待與喚醒案例的實現

     /*
      *  定義資源類,有2個成員變數
      *  name,sex
      *  同時有2個執行緒,對資源中的變數操作
      *  1個對name,age賦值
      *  2個對name,age做變數的輸出列印
      */
     public class Resource {
      public String name;
      public String sex;
      public boolean flag = false;
     }

     /*
      *  輸入的執行緒,對資源物件Resource中成員變數賦值
      *  一次賦值 張三,男
      *  下一次賦值 lisi,nv
      */
     public class Input implements Runnable {
      private Resource r ;
      
      public Input(Resource r){
        this.r = r;
      }
      
      public void run() {
        int i = 0 ;
        while(true){
          synchronized(r){
            //標記是true,等待
              if(r.flag){
                try{r.wait();}catch(Exception ex){}
              }
            
            if(i%2==0){
              r.name = "張三";
              r.sex = "男";
            }else{
              r.name = "lisi";
              r.sex = "nv";
            }
            //將對方執行緒喚醒,標記改為true
            r.flag = true;
            r.notify();
          }
          i++;
        }
      }

     }
     
     /*
      *  輸出執行緒,對資源物件Resource中成員變數,輸出值
      */
     public class Output implements Runnable {
      private Resource r ;
      
      public Output(Resource r){
        this.r = r;
      }
      public void run() {
        while(true){
          synchronized(r){  
            //判斷標記,是false,等待
          if(!r.flag){
            try{r.wait();}catch(Exception ex){}
            }
          System.out.println(r.name+".."+r.sex);
          //標記改成false,喚醒對方執行緒
          r.flag = false;
          r.notify();
          }
        }
      }

     }

     /*
      *  開啟輸入執行緒和輸出執行緒,實現賦值和列印值
      */
     public class ThreadDemo{
      public static void main(String[] args) {
        
        Resource r = new Resource();
        
        Input in = new Input(r);
        Output out = new Output(r);
        
        Thread tin = new Thread(in);
        Thread tout = new Thread(out);
        
        tin.start();
        tout.start();
      }
     }

輸出結果如下:(完成了協同工作,賦值完後輸出,輸出完後賦值的目的)

&n