java多線程

概述:

程序:

是爲完成特定任務,用某種語言編寫的一組指令的集合,即指一段靜態的代碼,靜態對象。java

進程:

是程序的一次執行過程,或是正在運行的一個程序,是一個動態的過程,有它自身的產生,存在和消亡的過程。-------生命週期android

線程:

進程可進一步細化爲線程,是一個程序內部的一條執行路徑
即:線程《線程(一個程序能夠有多個線程)
程序:靜態的代碼 進程:動態執行的程序
線程:進程中要同時幹幾件事時,每一件事的執行路徑成爲線程。
算法

並行:

多個CPU同時執行多個任務,好比:多我的同時作不一樣的事數據庫

併發:

一個CPU(採用時間片)同時執行多個任務,好比秒殺平臺,多我的作同件事安全

線程的相關API:

//獲取當前線程的名字
Thread.currentThread().getName()多線程

1.start():1.啓動當前線程2.調用線程中的run方法
2.run():一般須要重寫Thread類中的此方法,將建立的線程要執行的操做聲明在此方法中
3.currentThread():靜態方法,返回執行當前代碼的線程
4.getName():獲取當前線程的名字
5.setName():設置當前線程的名字
6.yield():主動釋放當前線程的執行權
7.join():在線程中插入執行另外一個線程,該線程被阻塞,直到插入執行的線程徹底執行完畢之後,該線程才繼續執行下去
8.stop():過期方法。當執行此方法時,強制結束當前線程。
9.sleep(long millitime):線程休眠一段時間
10.isAlive():判斷當前線程是否存活併發

判斷是不是多線程:

一條線程即爲一條執行路徑,即當能用一條路徑畫出來時即爲一個線程
例:以下看似既執行了方法一,又執行了方法2,可是其實質就是主線程在執行方法2和方法1這一條路徑,因此就是一個線程app

public class Sample{
        public void method1(String str){
            System.out.println(str);
        }

    public void method2(String str){
        method1(str);
    }

    public static void main(String[] args){
        Sample s = new Sample();
        s.method2("hello");
    }
}

java多線程<Thread>

線程的調度:

調度策略:
時間片:線程的調度採用時間片輪轉的方式
搶佔式:高優先級的線程搶佔CPU
Java的調度方法:
1.對於同優先級的線程組成先進先出隊列(先到先服務),使用時間片策略
2.對高優先級,使用優先調度的搶佔式策略ide

線程的優先級

等級:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5函數

方法:
getPriority():返回線程優先級
setPriority(int newPriority):改變線程的優先級

注意!:高優先級的線程要搶佔低優先級的線程的cpu的執行權。可是僅是從機率上來講的,高優先級的線程更有可能被執行。並不意味着只有高優先級的線程執行完之後,低優先級的線程才執行。

多線程的建立方式:

1. 方式1:繼承於Thread類

1.建立一個集成於Thread類的子類 (經過ctrl+o(override)輸入run查找run方法)
2.重寫Thread類的run()方法
3.建立Thread子類的對象
4.經過此對象調用start()方法

start與run方法的區別:

start方法的做用:1.啓動當前線程 2.調用當前線程的重寫的run方法(在主線程中生成子線程,有兩條線程)
調用start方法之後,一條路徑表明一個線程,同時執行兩線程時,由於時間片的輪換,因此執行過程隨機分配,且一個線程對象只能調用一次start方法。
run方法的做用:在主線程中調用之後,直接在主線程一條線程中執行了該線程中run的方法。(調用線程中的run方法,只調用run方法,並不新開線程)

總結:咱們不能經過run方法來新開一個線程,只能調用線程中重寫的run方法(能夠在線程中不斷的調用run方法,可是不能開啓子線程,即不能同時幹幾件事),start是開啓線程,再調用方法(即默認開啓一次線程,調用一次run方法,能夠同時執行幾件事)
java多線程<Thread>
多線程例子(火車站多窗口賣票問題)

package com.example.paoduantui.Thread;

    import android.view.Window;

    /**
     *
     * 建立三個窗口賣票,總票數爲100張,使用繼承自Thread方式
     * 用靜態變量保證三個線程的數據獨一份
     * 
     * 存在線程的安全問題,有待解決
     *
     * */

    public class ThreadDemo extends Thread{

        public static void main(String[] args){
            window t1 = new window();
            window t2 = new window();
            window t3 = new window();

            t1.setName("售票口1");
            t2.setName("售票口2");
            t3.setName("售票口3");

            t1.start();
            t2.start();
            t3.start();
        }

    }

    class window extends Thread{
        private static int ticket = 100; //將其加載在類的靜態區,全部線程共享該靜態變量

        @Override
        public void run() {
            while(true){
                if(ticket>0){
//                try {
//                    sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                    System.out.println(getName()+"當前售出第"+ticket+"張票");
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }

2. 方式2:實現Runable接口方式

1.建立一個實現了Runable接口的類
2.實現類去實現Runnable中的抽象方法:run()
3.建立實現類的對象
4.將此對象做爲參數傳遞到Thread類中的構造器中,建立Thread類的對象
5.經過Thread類的對象調用start()

具體操做,將一個類實現Runable接口,(插上接口一端)。
另一端,經過實現類的對象與線程對象經過此Runable接口插上接口實現

package com.example.paoduantui.Thread;

    public class ThreadDemo01 {

        public static  void main(String[] args){
            window1 w = new window1();

            //雖然有三個線程,可是隻有一個窗口類實現的Runnable方法,因爲三個線程共用一個window對象,因此自動共用100張票

            Thread t1=new Thread(w);
            Thread t2=new Thread(w);
            Thread t3=new Thread(w);

            t1.setName("窗口1");
            t2.setName("窗口2");
            t3.setName("窗口3");

            t1.start();
            t2.start();
            t3.start();
        }
    }

    class window1 implements Runnable{

        private int ticket = 100;

        @Override
        public void run() {
            while(true){
                if(ticket>0){
    //                try {
    //                    sleep(100);
    //                } catch (InterruptedException e) {
    //                    e.printStackTrace();
    //                }
                    System.out.println(Thread.currentThread().getName()+"當前售出第"+ticket+"張票");
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }

比較建立線程的兩種方式:
開發中,優先選擇實現Runable接口的方式
緣由
1:實現的方式沒有類的單繼承性的侷限性
2:實現的方式更適合用來處理多個線程有共享數據的狀況
聯繫:Thread也是實現自Runable,兩種方式都須要重寫run()方法,將線程要執行的邏輯聲明在run中

3.新增的兩種建立多線程方式

1.實現callable接口方式:

與使用runnable方式相比,callable功能更強大些:
runnable重寫的run方法不如callaalbe的call方法強大,call方法能夠有返回值
方法能夠拋出異常
支持泛型的返回值
須要藉助FutureTask類,好比獲取返回結果

package com.example.paoduantui.Thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 建立線程的方式三:實現callable接口。---JDK 5.0新增
 *是否多線程?否,就一個線程
 *
 * 比runable多一個FutureTask類,用來接收call方法的返回值。
 * 適用於須要從線程中接收返回值的形式
 * 
 * //callable實現新建線程的步驟:
 * 1.建立一個實現callable的實現類
 * 2.實現call方法,將此線程須要執行的操做聲明在call()中
 * 3.建立callable實現類的對象
 * 4.將callable接口實現類的對象做爲傳遞到FutureTask的構造器中,建立FutureTask的對象
 * 5.將FutureTask的對象做爲參數傳遞到Thread類的構造器中,建立Thread對象,並調用start方法啓動(經過FutureTask的對象調用方法get獲取線程中的call的返回值)
 * 
 * */

//實現callable接口的call方法
class NumThread implements Callable{

    private int sum=0;//

    //能夠拋出異常
    @Override
    public Object call() throws Exception {
        for(int i = 0;i<=100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName()+":"+i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {

    public static void main(String[] args){
        //new一個實現callable接口的對象
        NumThread numThread = new NumThread();

        //經過futureTask對象的get方法來接收futureTask的值
        FutureTask futureTask = new FutureTask(numThread);

        Thread t1 = new Thread(futureTask);
        t1.setName("線程1");
        t1.start();

        try {
            //get返回值即爲FutureTask構造器參數callable實現類重寫的call的返回值
           Object sum = futureTask.get();
           System.out.println(Thread.currentThread().getName()+":"+sum);
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

使用線程池的方式:

背景:

常常建立和銷燬,使用量特別大的資源,好比並髮狀況下的線程,對性能影響很大。

思路:

提早建立好多個線程,放入線程池之,使用時直接獲取,使用完放回池中。能夠避免頻繁建立銷燬,實現重複利用。相似生活中的公共交通工具。(數據庫鏈接池)

好處:

提升響應速度(減小了建立新線程的時間)
下降資源消耗(重複利用線程池中線程,不須要每次都建立)
便於線程管理
corePoolSize:核心池的大小
maximumPoolSize:最大線程數
keepAliveTime:線程沒有任務時最多保持多長時間後會終止
。。。。。。

JDK 5.0 起提供了線程池相關API:ExecutorService 和 Executors
ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor.
void execute(Runnable coommand):執行任務/命令,沒有返回值,通常用來執行Runnable
Futuresubmit(Callable task):執行任務,有返回值,通常又來執行Callable
void shutdown():關閉鏈接池。

Executors 工具類,線程池的工廠類,用於建立並返回不一樣類型的線程池
Executors.newCachedThreadPool() 建立一個可根據須要建立新線程的線程池
Executors.newFixedThreadPool(n) 建立一個可重用固定線程數的線程池
Executors.newSingleThreadExecutor() :建立一個只有一個線程的線程池
Executors.newScheduledThreadPool(n) 建立一個線程池,它可安排在給定延遲後運行命令或者按期地執行。

線程池構造批量線程代碼以下:

package com.example.paoduantui.Thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 建立線程的方式四:使用線程池(批量使用線程)
 *1.須要建立實現runnable或者callable接口方式的對象
 * 2.建立executorservice線程池
 * 3.將建立好的實現了runnable接口類的對象放入executorService對象的execute方法中執行。
 * 4.關閉線程池
 *
 * */

class NumberThread implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i<=100;i++){
            if (i % 2 ==0 )
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i<100; i++){
            if(i%2==1){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args){

        //建立固定線程個數爲十個的線程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        //new一個Runnable接口的對象
        NumberThread number = new NumberThread();
        NumberThread1 number1 = new NumberThread1();

        //執行線程,最多十個
        executorService.execute(number1);
        executorService.execute(number);//適合適用於Runnable

        //executorService.submit();//適合使用於Callable
        //關閉線程池
        executorService.shutdown();
    }

}

目前兩種方式要想調用新線程,都須要用到Thread中的start方法。

java virtual machine(JVM):java虛擬機內存結構

程序(一段靜態的代碼)——————》加載到內存中——————》進程(加載到內存中的代碼,動態的程序)
進程可細分爲多個線程,一個線程表明一個程序內部的一條執行路徑
每一個線程有其獨立的程序計數器(PC,指導着程序向下執行)與運行棧(本地變量等,本地方法等)
java多線程<Thread>

線程通訊方法:

wait()/ notify()/ notifayAll():此三個方法定義在Object類中的,由於這三個方法須要用到鎖,而鎖是任意對象都能充當的,因此這三個方法定義在Object類中。

因爲wait,notify,以及notifyAll都涉及到與鎖相關的操做

wait(在進入鎖住的區域之後阻塞等待,釋放鎖讓別的線程先進來操做)---- Obj.wait 進入Obj這個鎖住的區域的線程把鎖交出來原地等待通知
notify(因爲有不少鎖住的區域,因此須要將區域用鎖來標識,也涉及到鎖) ----- Obj.notify 新線程進入Obj這個區域進行操做並喚醒wait的線程

有點相似於我要拉粑粑,我先進了廁所關了門,可是發現廁全部牌子寫着不能用,因而我把廁所鎖給了別人,別人進來拉粑粑仍是修廁所不得而知,直到有人通知我廁所好了我再接着用。

因此wait,notify須要使用在有鎖的地方,也就是須要用synchronize關鍵字來標識的區域,即便用在同步代碼塊或者同步方法中,且爲了保證wait和notify的區域是同一個鎖住的區域,須要用鎖來標識,也就是鎖要相同的對象來充當

線程的分類:

java中的線程分爲兩類:1.守護線程(如垃圾回收線程,異常處理線程),2.用戶線程(如主線程)

若JVM中都是守護線程,當前JVM將退出。(形象理解,脣亡齒寒)

線程的生命週期:

JDK中用Thread.State類定義了線程的幾種狀態,以下:

線程生命週期的階段 描述
新建 當一個Thread類或其子類的對象被聲明並建立時,新生的線程對象處於新建狀態
就緒 處於新建狀態的線程被start後,將進入線程隊列等待CPU時間片,此時它已具有了運行的條件,只是沒分配到CPU資源
運行 當就緒的線程被調度並得到CPU資源時,便進入運行狀態,run方法定義了線程的操做和功能
阻塞 在某種特殊狀況下,被人爲掛起或執行輸入輸出操做時,讓出CPU並臨時終止本身的執行,進入阻塞狀態
死亡 線程完成了它的所有工做或線程被提早強制性地停止或出現異常致使結束

java多線程<Thread>

線程的同步:在同步代碼塊中,只能存在一個線程。

線程的安全問題:

什麼是線程安全問題呢?
線程安全問題是指,多個線程對同一個共享數據進行操做時,線程沒來得及更新共享數據,從而致使另外線程沒獲得最新的數據,從而產生線程安全問題。

上述例子中:建立三個窗口賣票,總票數爲100張票
1.賣票過程當中,出現了重票(票被反覆的賣出,ticket未被減小時就打印出了)錯票。
2.問題出現的緣由:當某個線程操做車票的過程當中,還沒有完成操做時,其餘線程參與進來,也來操做車票。(將此過程的代碼看做一個區域,當有線程進去時,裝鎖,不讓別的線程進去)
生動理解的例子:有一個廁所,有人進去了,可是沒有上鎖,因而別人不知道你進去了,別人也進去了對廁所也使用形成錯誤。
3.如何解決:當一個線程在操做ticket時,其餘線程不能參與進來,直到此線程的生命週期結束
4.在java中,咱們經過同步機制,來解決線程的安全問題。

方式一:同步代碼塊
使用同步監視器(鎖)
Synchronized(同步監視器){
//須要被同步的代碼
}
說明:

1. 操做共享數據的代碼(全部線程共享的數據的操做的代碼)(視做衛生間區域(全部人共享的廁所)),即爲須要共享的代碼(同步代碼塊,在同步代碼塊中,至關因而一個單線程,效率低)
2. 共享數據:多個線程共同操做的數據,好比公共廁所就類比共享數據
3. 同步監視器(俗稱:鎖):任何一個的對象均可以充當鎖。(可是爲了可讀性通常設置英文成lock)當鎖住之後只能有一個線程能進去(要求:多個線程必需要共用同一把鎖,好比火車上的廁所,同一個標誌表示有人)

Runable天生共享鎖,而Thread中須要用static對象或者this關鍵字或者當前類(window。class)來充當惟一鎖

方式二:同步方法
使用同步方法,對方法進行synchronized關鍵字修飾
將同步代碼塊提取出來成爲一個方法,用synchronized關鍵字修飾此方法。
對於runnable接口實現多線程,只須要將同步方法用synchronized修飾
而對於繼承自Thread方式,須要將同步方法用static和synchronized修飾,由於對象不惟一(鎖不惟一)

總結:1.同步方法仍然涉及到同步監視器,只是不須要咱們顯示的聲明。
2.非靜態的同步方法,同步監視器是this
靜態的同步方法,同步監視器是當前類自己。繼承自Thread。class

方式三:JDK5.0新增的lock鎖方法

package com.example.paoduantui.Thread;

import java.util.concurrent.locks.ReentrantLock;

class Window implements Runnable{
    private int ticket = 100;//定義一百張票
    //1.實例化鎖
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {

            while (true) {

                //2.調用鎖定方法lock
                lock.lock();

                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "售出第" + ticket + "張票");
                    ticket--;
                } else {
                    break;
                }
            }

        }
}

public class LockTest {

    public static void main(String[] args){
       Window w= new Window();

       Thread t1 = new Thread(w);
       Thread t2 = new Thread(w);
       Thread t3 = new Thread(w);

       t1.setName("窗口1");
       t2.setName("窗口1");
       t3.setName("窗口1");

       t1.start();
       t2.start();
       t3.start();
    }

}

總結:Synchronized與lock的異同?

相同:兩者均可以解決線程安全問題
不一樣:synchronized機制在執行完相應的代碼邏輯之後,自動的釋放同步監視器
lock須要手動的啓動同步(lock()),同時結束同步也須要手動的實現(unlock())(同時覺得着lock的方式更爲靈活)

優先使用順序:
LOCK-》同步代碼塊-》同步方法

判斷線程是否有安全問題,以及如何解決:

1.先判斷是否多線程
2.再判斷是否有共享數據
3.是否併發的對共享數據進行操做
4.選擇上述三種方法解決線程安全問題

例題:

package com.example.paoduantui.Thread;

    /***
     * 描述:甲乙同時往銀行存錢,存夠3000
     *
     *
     * */

    //帳戶
    class Account{
        private double balance;//餘額
        //構造器
        public Account(double balance) {
            this.balance = balance;
        }
        //存錢方法
        public synchronized void deposit(double amt){
            if(amt>0){
                balance +=amt;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"存錢成功,餘額爲:"+balance);
            }
        }
    }

    //兩個顧客線程
    class Customer extends Thread{
         private Account acct;

         public Customer(Account acct){
             this.acct = acct;
         }

        @Override
        public void run() {
            for (int i = 0;i<3;i++){
                acct.deposit(1000);
            }
        }
    }

    //主方法,之中new同一個帳戶,甲乙兩個存錢線程。
    public class AccountTest {

        public static void main(String[] args){
            Account acct = new Account(0);
            Customer c1 = new Customer(acct);
            Customer c2 = new Customer(acct);

            c1.setName("甲");
            c2.setName("乙");

            c1.start();
            c2.start();
        }

    }

解決單例模式的懶漢式的線程安全問題:

單例:只能經過靜態方法獲取一個實例,不能經過構造器來構造實例
1.構造器的私有化:
private Bank(){}//能夠在構造器中初始化東西
private static Bank instance = null;//初始化靜態實例

public static Bank getInstance(){
if(instance!=null){
instance = new Bank();
}
return instance;
}

假設有多個線程調用此單例,而調用的獲取單例的函數做爲操做共享單例的代碼塊並無解決線程的安全問題,會致使多個線程都判斷實例是否爲空,此時就會致使多個實例的產生,也就是單例模式的線程安全問題。

解決線程安全問題的思路:

  1. 將獲取單例的方法改寫成同部方法,即加上synchronized關鍵字,此時同步監視器爲當前類自己。(當有多個線程併發的獲取實例時,同時只能有一個線程獲取實例),解決了單例模式的線程安全問題。
  2. 用同步監視器包裹住同步代碼塊的方式。

懶漢式單例模式的模型,例如:生活中的限量版的搶購:
當一羣人併發的搶一個限量版的東西的時候,可能同時搶到了幾我的,他們同時進入了房間(同步代碼塊內)
可是隻有第一個拿到限量版東西的人才能到手,其他人都不能拿到,因此效率稍高的作法是,當東西被拿走時,咱們在門外立一塊牌子,售罄。
這樣就減小了線程的等待。即下面效率稍高的懶漢式寫法:

package com.example.paoduantui.Thread;

public class Bank {
    //私有化構造器
    private Bank(){}
    //初始化靜態實例化對象
    private static  Bank instance = null;

    //獲取單例實例,此種懶漢式單例模式存在線程不安全問題(從併發考慮)

    public static  Bank getInstance(){
        if(instance==null){
            instance = new Bank();
        }
        return  instance;
    }

    //同步方法模式的線程安全
    public static synchronized Bank getInstance1(){
        if(instance==null){
            instance = new Bank();
        }
        return  instance;
    }
    //同步代碼塊模式的線程安全(上鎖)
    public  static Bank getInstance2(){
        synchronized (Bank.class){
            if(instance==null){
                instance = new Bank();
            }
            return  instance;
        }
    }

    //效率更高的線程安全的懶漢式單例模式
    /**
     * 因爲當高併發調用單例模式的時候,相似於萬人奪寶,只有第一個進入房間的人才能拿到寶物,
     * 當多我的進入這個房間時,第一我的拿走了寶物,也就另外幾我的須要在同步代碼塊外等候,
     * 剩下的人只須要看到門口售罄的牌子即已知寶物已經被奪,能夠不用進入同步代碼塊內,提升了效率。
     * 
     * 
     * */
    public static Bank getInstance3(){
        if (instance==null){
            synchronized (Bank.class){
                if(instance==null){
                    instance = new Bank();
                }
            }
        }
        return  instance;
    }
}

線程的死鎖問題:

線程死鎖的理解:僵持,誰都不放手,一雙筷子,我一隻你一隻,都等對方放手(死鎖,二者都進入阻塞,誰都吃不了飯,進行不了下面吃飯的操做)
出現死鎖之後,不會出現提示,只是全部線程都處於阻塞狀態,沒法繼續

package com.example.paoduantui.Thread;

/**

  • 演示線程的死鎖問題
  • */
    public class Demo {

    public static void main(String[] args){

    final StringBuffer s1 = new StringBuffer();
    final StringBuffer s2 = new StringBuffer();
    
    new Thread(){
        @Override
        public void run() {
            //先拿鎖一,再拿鎖二
            synchronized (s1){
                s1.append("a");
                s2.append("1");
    
                synchronized (s2) {
                    s1.append("b");
                    s2.append("2");
    
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }
    }.start();
    
    //使用匿名內部類實現runnable接口的方式實現線程的建立
    new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (s2){
                s1.append("c");
                s2.append("3");
    
                synchronized (s1) {
                    s1.append("d");
                    s2.append("4");
    
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }
    }).start();

    }

}

運行結果:
1.先調用上面的線程,再調用下面的線程:
![](https://s4.51cto.com/images/blog/202012/05/9a515a2a7672076232ccc640cb317afa.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
2.出現死鎖
![](https://s4.51cto.com/images/blog/202012/05/d19c09bede6d7510c2dc444839612cd0.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
3.先調用下面的線程,再調用上面的線程。
![](https://s4.51cto.com/images/blog/202012/05/cf2a6e31c94fa7f611544cf9b31da012.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
## 死鎖的解決辦法:
> 1.減小同步共享變量
2.採用專門的算法,多個線程之間規定前後執行的順序,規避死鎖問題
3.減小鎖的嵌套。

-----

> # 線程的通訊

通訊經常使用方法:

| 通訊方法 | 描述 | 
| -------- | -------- | -------- |
| wait     | 一旦執行此方法,當前線程就進入阻塞狀態,並釋放同步監視器    | 
|notify|一旦執行此方法,就會喚醒被wait的一個線程,若是有多個線程,就喚醒優先級高的線程
|notifyAll|一旦執行此方法,就會喚醒全部被wait()的線程

## 使用前提:這三個方法均只能使用在同步代碼塊或者同步方法中。

package com.example.paoduantui.Thread;

/**

  • 線程通訊的例子:使用兩個線程打印1—100,線程1,線程2交替打印
  • 當咱們不採起線程之間的通訊時,沒法達到線程1,2交替打印(cpu的控制權,是自動分配的)
  • 若想達到線程1,2交替打印,須要:
  • 1.當線程1獲取鎖之後,進入代碼塊裏將number++(數字打印並增長)操做完之後,爲了保證下個鎖爲線程2全部,須要將線程1阻塞(線程1你等等wait())。(輸出1,number爲2)
  • 2.當線程2獲取鎖之後,此時線程1已經不能進入同步代碼塊中了,因此,爲了讓線程1繼續搶佔下一把鎖,須要讓線程1的阻塞狀態取消(通知線程1不用等了notify()及notifyAll()),即應該在進入同步代碼塊時取消線程1的阻塞。
  • */

class Number implements Runnable{

private int number = 1;//設置共享數據(線程之間對於共享數據的共享即爲通訊)

//對共享數據進行操做的代碼塊,須要線程安全
@Override
public synchronized void run() {

    while(true){
        //使得線程交替等待以及通知交替解等待
        notify();//省略了this.notify()關鍵字
        if(number<100){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":"+number);
            number++;
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            break;
        }
    }
}

}

public class CommunicationTest {

public static void main(String[] args){
    //建立runnable對象
    Number number = new Number();

    //建立線程,並實現runnable接口
    Thread t1 = new Thread(number);
    Thread t2 = new Thread(number);

    //給線程設置名字
    t1.setName("線程1");
    t2.setName("線程2");

    //開啓線程
    t1.start();
    t2.start();

}

}

## sleep和wait的異同:

> 相同點:一旦執行方法之後,都會使得當前的進程進入阻塞狀態
不一樣點:
1.兩個方法聲明的位置不一樣,Thread類中聲明sleep,Object類中聲明wait。
2.調用的要求不一樣,sleep能夠在任何須要的場景下調用,wait必須使用在同步代碼塊或者同步方法中
3.關因而否釋放同步監視器,若是兩個方法都使用在同步代碼塊或同步方法中,sleep不會釋放,wait會釋放

## 經典例題:生產者/消費者問題:
生產者(Priductor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品,店員一次只能持有固定數量的產品(好比20個),若是生產者視圖生產更多的產品,店員會叫生產者停一下,若是店中有空位放產品了再通知生產者繼續生產:若是店中沒有產品了,店員會告訴消費者等一下,若是店中有產品了再通知消費者來取走產品。

這裏可能出現兩個問題:
生產者比消費者快的時候,消費者會漏掉一些數據沒有收到。
消費者比生產者快時,消費者會去相同的數據。

package com.example.paoduantui.Thread;

/**

  • 線程通訊的應用:生產者/消費者問題
  • 1.是不是多線程問題?是的,有生產者線程和消費者線程(多線程的建立,四種方式)
  • 2.多線程問題是否存在共享數據? 存在共享數據----產品(同步方法,同步代碼塊,lock鎖)
  • 3.多線程是否存在線程安全問題? 存在----都對共享數據產品進行了操做。(三種方法)
  • 4.是否存在線程間的通訊,是,若是生產多了到20時,須要通知中止生產(wait)。(線程之間的通訊問題,須要wait,notify等)
  • */

    class Clerk{

    private int productCount = 0;
    
    //生產產品
    public synchronized void produceProduct() {
    
        if(productCount<20) {
            productCount++;
    
            System.out.println(Thread.currentThread().getName()+":開始生產第"+productCount+"個產品");
            notify();
        }else{
            //當有20個時,等待wait
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    //消費產品
    public synchronized void consumeProduct() {
        if (productCount>0){
            System.out.println(Thread.currentThread().getName()+":開始消費第"+productCount+"個產品");
            productCount--;
            notify();
        }else{
            //當0個時等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    }

    class Producer extends Thread{//生產者線程

    private Clerk clerk;
    
    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }
    
    @Override
    public void run() {
    
        try {
            sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+";開始生產產品......");
    
        while(true){
            clerk.produceProduct();
        }
    }

    }

    class Consumer implements Runnable{//消費者線程

    private Clerk clerk;
    
    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }
    
    @Override
    public void run() {
    
        System.out.println(Thread.currentThread().getName()+":開始消費產品");
    
        while(true){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            clerk.consumeProduct();
        }
    
    }

    }

    public class ProductTest {

    public static void main(String[] args){
        Clerk clerk = new Clerk();
    
        Producer p1 = new Producer(clerk);
        p1.setName("生產者1");
    
        Consumer c1 = new Consumer(clerk);
        Thread t1 = new Thread(c1);
        t1.setName("消費者1");
    
        p1.start();
        t1.start();
    
    }

    }

相關文章
相關標籤/搜索