Java零基礎之多執行緒part2(新手可看)

耳東chen發表於2020-11-19

Part2

執行緒的生命週期

  • 新建
  • 就緒
  • 執行
  • 阻塞
  • 死亡

在這裡插入圖片描述

執行緒的同步

生活例項:廁所存在X一個坑位

正常情況下多執行緒的執行方式為:

A進入 X坑位----執行pullshit( )----出坑----→下一個物件進X坑。。。。。

但是不安全的多執行緒可能會出現:

A進入坑位==執行pullshit( )【未出坑】—B很著急,進入X坑–同時pullshit

此時即為執行緒不安全,可能會造成髒資料等情況

【解決方案】:加鎖

Java中解決方法:同步鎖機制,解決執行緒安全

  • 同步程式碼塊

    synchronized(同步監視器){

    //需要同步的程式碼

    說明:

    • 操作共享資料的程式碼,即需要被同步的程式碼

      【注意同步、的範圍】

    • 共享資料:多個執行緒共同操作的變數:比如上文的ticnum

    • 同步監視器:俗稱—:任何一個類的物件都可以當鎖

      要求多個執行緒必須共用同一把鎖

      • Runnable中考慮 this充當同步鎖

      • 繼承Thread類中,慎用 this

        考慮當前類.class充當同步鎖

    }

    public class SynchronizedDemo {
        public static void main(String[] args) {
            TicketAdd ticketAdd = new TicketAdd();
            Thread t1 = new Thread(ticketAdd);
            Thread t2 = new Thread(ticketAdd);
    
            t1.setName("Thread==1:");
            t2.setName("Thread==2:");
            t1.start();
            t2.start();
        }
    }
    
    /*
    * 為何不將同步鎖把while(true)也包含在內
    * 個人理解:
    * 在不將while包括在鎖內的時候,兩個執行緒在呼叫的時候 具體看cpu資源分配,可能執行執行緒1 ,也可能執行執行緒2
    * 但是都只會執行一次其中的判斷,要麼賣出一張票,要麼無票,若有票,繼續看cpu執行權
    *
    * 然而將while包含在內的時候,如果先start執行緒一,那麼此時,執行緒同步鎖發揮作用,會一直將執行緒一執行,直到沒有票break了,才會去執行執行緒二,但是此時執行緒2 已經沒票了
    * */
    
    class TicketAdd implements Runnable{  
        private int ticnum = 10000;
        Object object = new Object();
        @Override
        public void run() {
            while (true){
            synchronized(object){
                if (ticnum>0){
                    System.out.println(Thread.currentThread().getName()+"sold:"+ticnum);
                    ticnum--;
                }else {
                    System.out.println(Thread.currentThread().getName()+"沒票了");
                    break;
                }
            }
         }
    
        }
    }
    
  • 同步方法

    如果操作共享資料的程式碼完整的宣告在同一個方法中

    不妨將此方法宣告為同步

    • Runnable
    
    public class window2 {
        public static void main(String[] args) {
            windows w1 = new windows();
            Thread t1 = new Thread(w1);
            Thread t2 = new Thread(w1);
            t1.setName("Thread==1:");
            t2.setName("Thread==2:");
            t1.start();
            t2.start();
        }
    }
    
    class windows implements Runnable{
    
        private int ticnum = 10000;
        @Override
        public void run() {
            while (true){
                sold();
            }
        }
        private synchronized void sold(){//採用方法同步鎖  ==此時監視器為this,即當前物件
            if (ticnum>0){
                System.out.println(Thread.currentThread().getName()+"sold:"+ticnum);
                ticnum--;
            }else {
                System.out.println(Thread.currentThread().getName()+"沒票了");
                System.exit(1);
            }
        }
    }
    
    • Extends Thread
    public class window3 {
        public static void main(String[] args) {
            windowExtends w1 =new windowExtends();
            windowExtends w2 =new windowExtends();
            w1.setName("W1:");
            w2.setName("W2:");
            w1.start();
            w2.start();
        }
    }
    class windowExtends extends Thread{
    
        private static int ticnum = 1000;
        public void run() {
            while (true){
                sold2();
            }
        }
        //此時同步監視器為windowExtedns.class
        private static synchronized void sold2(){
            if (ticnum>0){
                System.out.println(Thread.currentThread().getName()+"sold:"+ticnum);
                ticnum--;
            }else {
                System.out.println(Thread.currentThread().getName()+"沒票了");
                System.exit(1);
            }
        }
    }
    

    總結 :

    1. 同步方法仍然涉及到同步監視器,但是不需要顯式宣告

      1. 非靜態同步方法,同步監視器為:this

        上述Runnable介面實現的就是非靜態同步方法

      2. 靜態的同步方法,同步監視器為:當前類本身

        上述Extedns Thread實現的就是靜態同步方法

死鎖

死鎖的理解:

解決方法:

Lock鎖–ReentranLock–

解決執行緒安全的方法三

Lock介面下的ReentranLock

class Rdemo implements Runnable{
    private ReentrantLock lock = new ReentrantLock();//建立一個鎖
    @Override
    public void run() {
        try {
            lock.lock();//上鎖,後續的程式碼都是鎖定的
            System.out.println("helo");
        }finally {
            lock.unlock();//解鎖
        }
    }
}

synchronized和ReentranLocked的不同點

  • lock手動鎖定和解鎖
  • synchronized自動解鎖

優先使用順序:

執行緒通訊

  • wait():執行此方法,當前執行緒就會進入阻塞狀態,並釋放同步監視器
  • notify():喚醒另一個被wait的執行緒
  • notifyAll():喚醒其他所有wait的執行緒

sleep和wait方法的異同

  • 同:都可以讓當前執行緒進入阻塞狀態

  • 異:

    • 宣告位置不同

      sleep在Thread類中宣告

      wait在Object類中宣告

    • 呼叫範圍不同

    sleep可以在任何情況下使用

    wait只能在同步程式碼塊或者同步方法中使用

    • 是否釋放同步監視器

      當他們都在同步程式碼塊或者同步方法中使用時,sleep不釋放同步監視器

生產消費者模型

存在兩個執行緒 生產和消費

可能出現的問題:生產或消費的阻塞期,產生髒資料

程式碼實現Version1

public class PandC2 {
    public static void main(String[] args) {
        Clerk2a clerk2a = new Clerk2a();
        Customer1 c1 = new Customer1(clerk2a);
        Customer2 c2 = new Customer2(clerk2a);
        Productor1 p1 = new Productor1(clerk2a);

        Thread t1 = new Thread(c1);
        Thread t12 = new Thread(c2);
        Thread t2 = new Thread(p1);


        t1.setName("消費者執行緒");
        t12.setName("消費者執行緒2");
        t2.setName("生產者執行緒");
        t1.start();
        t12.start();
        t2.start();


    }
}

class Clerk2a{
    private int num = 0;
    public synchronized void shengchan(){
        if (num<20){

            num++;
            System.out.println(Thread.currentThread().getName()+"生產第"+num+"個產品");
            notify();
        }else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    
    public synchronized void xiaofei(){
        if (num>0){
            System.out.println(Thread.currentThread().getName()+"消費第"+num+"個產品");
            num--;
            notify();
        }else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
class Productor1  implements Runnable{
    private Clerk2a clerk2a;
    public Productor1(Clerk2a clerk2a) {
        this.clerk2a = clerk2a;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk2a.shengchan();
        }


    }
}

class Customer1 implements Runnable{
    private Clerk2a clerk2a;
    public Customer1(Clerk2a clerk2a) {
        this.clerk2a = clerk2a;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk2a.xiaofei();
        }
    }
}

class Customer2 implements Runnable{
    private Clerk2a clerk2a;

    public Customer2(Clerk2a clerk2a) {
        this.clerk2a = clerk2a;
    }

    @Override
    public void run() {
        while (true){


            clerk2a.xiaofei();
        }
    }
}

JDK5 新增的兩種建立多執行緒方式

實現Callable介面

相比於Runnable介面的優勢

  • 有返回值

  • 可以丟擲異常

  • 支援泛型

/*
 * 1. 建立一個實現Callable介面的類A
 * 2. 重寫cal方法
 * 3. 建立Callable介面實現類A的物件
 * 4. 將A的物件作為引數傳遞到FutureTask作為引數生成一個新的FutureTask物件B
 * 5. 同理B作為引數生成Thread類物件,並呼叫start方法
 * */
public class MulThreadPool {
    public static void main(String[] args) {
        A a = new A();
        FutureTask futureTask = new FutureTask(a);
        Thread t1 = new Thread(futureTask);
        t1.setName("cal執行緒");
        t1.start();
    }
}
class A implements Callable{
    @Override
    public Object call() throws Exception {
        System.out.println("hello world");
        return null;
    }
}

使用執行緒池

ExecutorService 是真正的執行緒池介面,但由於他只是介面

需要通過Executors工廠類的方法來決定建立不同的具體物件

工廠類方法包括(自行查閱API檢視具體,只描述了最常用的方法)

  • Executors.newCachedThreadPool( )

  • Executors.newFixedThreadPool( )

    建立可重複固定執行緒數的執行緒池

  • Executors.newSingleThreadPool( )

  • Executors.newScheduledThreadPool( )

public class MulThreadPool{
    public static void main(String[] args) {
        //1. 根據需求建立執行緒池
        ExecutorService service = Executors.newFixedThreadPool(5);
        System.out.println(service.getClass());
        //設定執行緒池的屬性
        ThreadPoolExecutor tpe = (ThreadPoolExecutor) service;

       // 2. 執行指定的執行緒操作,需要提供實現了Runnable介面或者Callable介面的實現類的物件
        service.execute(new N());
        service.execute(new M());
      //  3. 關閉執行緒池
        service.shutdown();
    }
}
class M implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"hello world");
    }
}
class N implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"hello world");
    }
}

如有任何疑問,歡迎留言

相關文章