1. 程式人生 > >Java多執行緒實現以及執行緒安全筆記

Java多執行緒實現以及執行緒安全筆記

Java虛擬機器允許應用程式併發地執行多個執行緒。

以下為多執行緒的實現常用的2種方法

(1)繼承Thread類,重寫run()方法

     Thread本質上也是實現了Runnable介面的一個例項,代表一個執行緒的例項。啟用執行緒的唯一方法就是通過Thread類的start()方法。呼叫start()方法後並不是立即執行多執行緒程式碼,而是使得該執行緒變為可執行態(Running),什麼時候執行是由作業系統決定的。

class MyThread extends Thread{//建立函式體
    public void run(){
        System.out.println("Thread body");//執行緒函式體
    }
}
public class Test{
    public static void main(String[] args){
        MyThread thread = new MyThread();
        thread.start();//執行緒開啟 
    }
}

(2)實現Runnable介面,並實現該介面的run()方法

主要步驟

  • 自定義類並實現Runable介面,實現run()方法。
  • 建立Thread物件,用實現Runnable介面的物件作為引數例項化該Thread物件
  • 呼叫Thread的start()方法
class MyThread implements Runnable{
    public void run(){
        System.out.println("Thread body");
    }
}
public class Test{
    public static void main(String []args){
        MyThread thread = new MyThread();
        Thread t = new Thread(thread);
        t.start();//開啟執行緒
    }
}

這兩種方法最終都是通過Thread 的物件的API來控制執行緒的。

執行緒狀態: 

  • NEW  新建狀態

  • RUNNABLE  執行狀態

  • BLOCK 受阻塞   執行緒具有CPU的執行資格

  • WAIT (無限期)等待  object的方法   notify(喚醒)  放棄CPU

  • TIMED_WAITING 休眠  sleep   放棄CPU

  • TERMINATED 死亡

執行緒池

  • 節約開銷

  • 由執行緒池工廠建立的,再呼叫執行緒池中的方法獲取執行緒,再通過執行緒去執行方法

  • 在java.util.concurrent 中的Executors 類

    • 實現執行緒池程式,使用供現場類Executors中的靜態方法建立執行緒物件,指定執行緒個數

      static ExectorService newFixedThreadPool(int 個數) 返回執行緒池物件

      返回的是ExecutorService介面的實現類(執行緒池物件)

      介面實現類物件,呼叫方法submit (Runnable r) 提交執行緒執行任務

  • run方法沒有返回值,不能拋異常

執行緒開啟虛擬棧空間,棧記憶體是執行緒私有的

建立執行緒的目的:為了建立程式單獨的執行路徑

Thread.currentThread();返回正在執行的執行緒物件

執行緒安全

  • 單執行緒沒有安全問題

  • 多執行緒同時操作同一個共享資料,往往出現安全問題

    • 解決:當一個執行緒進入資料操作的時候,無論是否休眠,其他執行緒只能等待。保證只有一個執行緒在操作

      • 同步程式碼塊:

        • synchronized(任意物件){ 執行緒要操作的共享資料 }

        • 同步物件,任意物件。物件:同步鎖,物件監視器 obj

        • 同步保證安全性:沒有鎖的執行緒不能執行,只能等。加了同步後,執行緒進同步判斷鎖,獲取鎖,出同步釋放鎖,導致程式執行速度的下降。 

        • 執行緒遇到同步程式碼塊後,執行緒判斷同步鎖還有沒有。

          • 如果有,獲取鎖,進入同步中,去執行,執行完畢後將鎖物件還回去。在同步中執行緒,進行了休眠,此時另一個執行緒,會執行。

          • 沒有鎖的執行緒,不能進入同步中執行,被阻擋在同步程式碼塊的外面。在同步中的執行緒,不出去同步,不會釋放鎖。

      • 同步方法:程式碼簡潔,將執行緒共享資料,和同步,抽取到一個方法中。物件鎖是本類物件引用this.如果方法為靜態,鎖是本類自己.class 屬性

電影院賣票,排隊上廁所原理

package ticket;
/**
* 多執行緒併發訪問同一個資料資源
* 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();
    }
}

package ticket;
public class Tickets implements Runnable{
    private int ticket = 100;
    //同步程式碼塊
//     private Object obj = new Object();
//     @Override
//     public void run() {
//             while (true){
//             //對票數判斷,大於0,可以出售
//             synchronized (obj){//不寫匿名物件是因為每次迴圈時,物件就變了
//                 if(ticket > 0){
//                     try {
//                         Thread.sleep(10);
//                     } catch (InterruptedException e) {
//                     }
//                 System.out.println(Thread.currentThread().getName()+"出售第"+ticket--);
//                }
//             }
//         }
//     }
//同步方法
    @Override
    public void run() {
        while (true){
            payTicket();
        }
    }
    //對票數判斷,大於0,可以出售
    public synchronized void payTicket(){
        if(ticket > 0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread().getName()+"出售第"+ticket--);
        }
    }
}

Lock

  • 可以替代synchronized,比他更靈活

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Tickets implements Runnable{
    private int ticket = 100;
    //在類的成員位置,建立Lock介面實現類的物件
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){    
            lock.lock();
            //對票數判斷,大於0,可以出售
            if(ticket > 0){
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"出售第"+ticket--);
                } catch (InterruptedException e) {
                }finally {
                    lock.unlock();
}}}}}

死鎖

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

synchronized(A 鎖){
    synchronized(B 鎖){
    }
}
  • 前提:必須是多執行緒的,出現同步巢狀

  • 執行緒進入同步獲取鎖,不出去同步,不會釋放鎖

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

public class DeadLock implements Runnable{
    private int i = 0;
    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 LockA {
    private LockA(){}
    public static final LockA lockA = new LockA();
}
public class LockB {
    private LockB(){}
    public static final LockB lockB = new LockB();
}

等待喚醒機制

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

  • wait() 無限等

  • notify() 喚醒,一次只喚醒一個,任意的

  • notifyAll() 喚醒全部

 //保證兩個執行緒交替輸入輸出值
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();
    }
}
public class Resource {
    public String name;
    public String sex;
    public boolean flag = false;
}
public class Input implements Runnable {
    private Resource resource ;
    public Input(Resource r){
        resource = r;
    }
    //flag 為ture表示賦值完成,false為取值完成
    @Override
    public void run() {
        int i = 0;
        while(true){
            synchronized (resource){
                //標記是true,等待
                if(resource.flag){
                    try{resource.wait();}catch (Exception e){}
                }
                if(i%2 ==0){
                    resource.name = "張";
                    resource.sex = "男";
                }else{
                    resource.name = "li";
                    resource.sex = "nv";
                }
                //將對方執行緒喚醒,標記該為true
                resource.flag = true;
                resource.notify();
            }
            i++;
        }
    }
}

public class Output implements Runnable {
    private Resource r ;//new物件之後輸出物件預設值,因為main方法中的物件和這物件不是一個。
    public Output (Resource r){
        this.r = r;
    }
    @Override
    public void run() {
        while(true){
            synchronized (r){
                // 判斷標記,是false,等待
                if(!r.flag){
                    try {
                        r.wait();
                    }catch (Exception e){}
                }
                System.out.println(r.name+".."+r.sex);
                //標記改成false,喚醒對方執行緒
                r.flag = false;
                r.notify();
            }
        }
    }
}