Java多線程講解與實例

多線程(Thread)


高可用 高性能 高併發

線程簡介

在這裏插入圖片描述

  • 方法間調用:普通方法調用,從哪裏來到那裏去,閉合的一條路徑
  • 多線程使用:開闢了多條路徑

Process與Thread

區別 進程 線程
根本區別 作爲資源分配的單位 調度和執行的單位
開銷 每個進程都有獨立的代碼和數據空間(進程上下文),進程間的切換會有較大的開銷。 線程可以看成是輕量級的進程,同一類線程共享代碼和數據空間,每個線程有獨立地運行和棧程序計數器(PC),線程切換的開銷小。
所處環境 在操作系統中能同時運行多個任務(程序) 在同一應用程序中有多個順序流同時執行
分配內存 系統在運行的時候會爲每個進程分配不同的內存區域 除了CPU之外,不會爲線程分配內存(線程所使用的資源是它所屬的進程的資源),線程組只能共享
包含關係 沒有線程的進程是可以被看做單線程的,如果一個進程內擁有多個線程,則執行過程不是一條線的,而是多條線(線程)共同完成的。 線程是進程的一部分,所以線程有的時候被稱爲是輕權進程或者輕量級進程。

注意:很多多線程是模擬出來的,真正的多線程是指由多個cpu,即多核,如服務器。如果是模擬出來的多線程,即一個cpu的情況下,在同一個時間點,cpu只能執行一個代碼,因爲切換的很快,所以就有同時執行的錯覺。

核心概念

  • 線程就是獨立的執行路徑

  • 在程序運行時,即使沒有自己創建線程,後臺也會存在多個線程,如gc線程,主線程。

  • main()稱之爲主線程,爲系統的入口點,用於執行整個程序。

  • 在一個進程中,如果開闢了多個線程,線程的運行由調度器安排調度,調度器是與操作系統緊密相關的,先後順序是不能人爲的干預的。

  • 對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制

  • 線程會帶來額外的開銷,如cpu調度時間,併發控制開銷

  • 每個線程在自己的工作內存交互,加載和存儲主內存控制不當會造成數據不一致。

線程實現

線程創建三種方式

  1. 繼承Thread類

  2. 實現Runnable接口

  3. 實現Callable接口
線程的創建Thread
package com.junwei.thread;

/**
 * @Auther: wangjunwei
 * @Description: Thread多線程 線程的創建
 * 1、創建:繼承Thread 重寫 Run
 * 2、啓動:創建子類對象 start
 * @Date: Created in 10:03 2018/12/26
 */
public class StartThread extends Thread {
    /**
     * @Author: wangjunwei
     * @Date: Created in 10:05 2018/12/26
     * @Description: 重寫run方法
     */

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            System.out.println("聽歌!");
        }
    }

    public static void main(String[] args) {
        //創建子類對象
        StartThread s1 = new StartThread();
        //開啓線程 start不保證立即運行 由cpu進行安排 run普通調用
        s1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("coding!");
        }
    }
}

多線程下載圖片
package com.junwei.thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * @Auther: wangjunwei
 * @Description: 網絡圖片下載
 * 圖片下載工具類
 * @Date: Created in 10:36 2018/12/26
 */
public class WebDownLoader01 {
    /**
     * @param url       1
     * @param localPath 2
     * @return : void
     * @Author: wangjunwei
     * @Date: Created in 10:44 2018/12/26
     * @Description: 下載圖片
     */

    protected void downLoad(String url, String localPath) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(localPath));
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

package com.junwei.thread;

import lombok.*;

/**
 * @Auther: wangjunwei
 * @Description: 開啓多線程下載圖片
 * @Date: Created in 10:47 2018/12/26
 */
@EqualsAndHashCode(callSuper = true)
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Data
public class TDownLoader01 extends Thread {
    /**
     * 圖片url
     */
    private String url;
    /**
     * 圖片存貯路徑及文件名
     */
    private String localPath;

    /**
     * @return : void
     * @Author: wangjunwei
     * @Date: Created in 10:49 2018/12/26
     * @Description: 重寫run方法
     */

    @Override
    public void run() {
        //實例化下載工具對象
        WebDownLoader01 wdl = new WebDownLoader01();
        wdl.downLoad(url,localPath);
        System.out.println(localPath);
    }

    /**
     * @param args 1
     * @return : void
     * @Author: wangjunwei
     * @Date: Created in 10:52 2018/12/26
     * @Description: 主函數
     */

    public static void main(String[] args) {
         //實例化線程對象
        TDownLoader01 td1 = new TDownLoader01("url","C:\\Users\\86241\\Desktop\\IOTest(2)/1.jpg");
        TDownLoader01 td2 = new TDownLoader01("url","C:\\Users\\86241\\Desktop\\IOTest(2)/2.jpg");
        TDownLoader01 td3 = new TDownLoader01("url","C:\\Users\\86241\\Desktop\\IOTest(2)/3.jpg");
        td1.start();
        td2.start();
        td3.start();
    }
}

Runnable
package com.junwei.thread;

/**
 * @Auther: wangjunwei
 * @Description: 創建線程方式二:
 * 1、創建:實現Runnable + 重寫run
 * 2、啓動:創新實現類對象+ Threadl類start
 *
 * 推薦方式:
 * 避免單繼承的侷限性,優先使用接口
 * 方便資源共享
 * @Date: Created in 12:22 2018/12/26
 */
public class StartRun implements Runnable {
     /**
      * @Author: wangjunwei
      * @Date: Created in 12:24 2018/12/26
      * @Description: 重寫run方法
      */

    public void run() {
        System.out.println("聽歌!");
    }

    public static void main(String[] args) {
        /*//創建實現類對象
        StartRun st = new StartRun();
        //創建代理類對象
        Thread t = new Thread(st);
        //啓動
        t.start();//開啓線程*/
        new Thread(new StartRun()).start();
        System.out.println("coding!");
    }
}

  • 實現接口Runnable具有多線程能力
  • 啓動線程:傳入目標對象+Thread對象.start()
  • 推薦使用:oop多實現,靈活方便,同一份對象的代理
package com.junwei.thread;

/**
 * @Auther: wangjunwei
 * @Description: 模擬龜兔賽跑
 * @Date: Created in 12:47 2018/12/26
 */
public class Racer implements Runnable {
    /**勝利者*/
    private String winner;

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->" + i++);
            boolean flag = gameOver(i);
            if (flag) {
                break;
            }
        }
    }

    private boolean gameOver(int steps) {
        if (winner != null) {
            return true;
        } else {
            if (steps == 100) {
                winner = Thread.currentThread().getName();
                System.out.println("winner==>" + winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Racer racer = new Racer();
        new Thread(racer, "烏龜").start();
        new Thread(racer, "兔子").start();
    }
}

實現Callable
class CDownLoader implements Callable{
    private String url;
    private String fname;
    public CDownLoader(String url, String fname){
        this.url=url;
        this.fname=fname;
    }
    public Object call()throws Exception{
        WebDownLoader downLoader = new WebDownLoader();
        downLoader.downLoad(url, fname);
        return true;
    }
}
  1. 創建目標對象:
  2. 創建執行服務:
  3. 提交執行:
  4. 獲取結果:
  5. 關閉服務:
package com.junwei.thread;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.concurrent.*;

/**
 * @Auther: wangjunwei
 * @Description: 
 * @Date: Created in 14:01 2018/12/26
 */
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CallAbleTest implements Callable<Boolean> {
    private String url;
    private String name;
    @Override
    public Boolean call() throws Exception {
        WebDownLoader01 wd = new WebDownLoader01();
        wd.downLoad(url,name);
        System.out.println(name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallAbleTest cd1 = new CallAbleTest();
        CallAbleTest cd2 = new CallAbleTest();
        CallAbleTest cd3 = new CallAbleTest();
        //創建執行服務 線程池
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交執行結果
        Future<Boolean> submit1 = ser.submit(cd1);
        Future<Boolean> submit2 = ser.submit(cd2);
        Future<Boolean> submit3 = ser.submit(cd3);
        //獲取結果
        Boolean r1 = submit1.get();
        Boolean r2 = submit2.get();
        Boolean r3 = submit3.get();
        //關閉服務
        ser.shutdownNow();
    }
}

靜態代理
package com.junwei.thread;

import lombok.AllArgsConstructor;

/**
 * @Auther: wangjunwei
 * @Description: 靜態代理
 * 接口:
 * 1、真實角色
 * 2、代理角色
 * @Date: Created in 14:36 2018/12/26
 */
public class StaticProxy {
    public static void main(String[] args) {
        new WeddingCompany(new You()).happyMarry();
        /*new Thread(線程對象).start()*/
    }
}

interface Marry{
    void happyMarry();
}
 /**
  * @Author: wangjunwei
  * @Date: Created in 14:38 2018/12/26
  * @Description: 真實角色
  */

class You implements Marry{

    @Override
    public void happyMarry() {
        System.out.println("新婚快樂!");
    }
}
 /**
  * @Author: wangjunwei
  * @Date: Created in 14:39 2018/12/26
  * @Description: 代理角色
  */
@AllArgsConstructor
class WeddingCompany implements Marry{
    //真實角色
     private Marry target;
     @Override
    public void happyMarry() {
        ready();
        this.target.happyMarry();   
        after();
    }

     private void after() {
         System.out.println("婚禮完成!");
     }

     private void ready() {
         System.out.println("佈置婚禮!");
     }
 }

線程狀態

在這裏插入圖片描述
在這裏插入圖片描述

線程方法
  • sleep()
    • 使線程停止運行一段時間,將處於阻塞狀態
    • 如果調用了sleep方法之後,沒有其他等待執行的線程,這個時候當前線程不會馬上恢復執行!
  • join()
    • 阻塞指定線程等到另一個線程完成以後再繼續執行
  • yield()
    • 讓當前正在執行線程暫停,不是阻塞線程,而是將線程轉入就緒狀態
    • 調用了yield方法之後,如果沒有其他等待執行的線程,此時當前線程就會馬上恢復執行!
  • setDaemon()
    • 可以將指定的線程設置成後臺線程,守護線程
    • 創建用戶線程的線程結束時,後臺線程也隨之消亡
    • 只能在線程啓動之前把它設爲後臺線程
  • setPriority(int newPriority) getPriority()
    • 線程的優先級代表的是概率
    • 範圍從1到10,默認爲5
  • stop()停止線程
    • 不推薦使用
多線程的終止
  • 不使用JDK提供的stop()/destrory()方法(它們本身也被JDK廢棄了)
  • 提供一個boolean類型的終止變量,當這個變量置爲false,則終止線程的運行
線程的暫停
  • sleep(時間)制定當前線程阻塞的毫秒數
  • sleep存在異常InterruptedException
  • sleep時間達到後線程進入就緒狀態
  • sleep可以模擬網絡延時、倒計時等
  • 每一個對象都有一個所,sleep不會釋放鎖
線程的禮讓
  • 禮讓線程,讓當前正在執行線程暫停
  • 不是阻塞線程,而是將線程從運行狀態轉入就緒狀態
  • 讓cpu調度器重新調度
join插隊線程
  • join合併線程,待此線程執行完成後,再執行其他線程,其他線程阻塞
多線程優先級

Java提供一個線程調度器來監控程序中啓動後進入就緒狀態的所有線程。線程調度器按照線程的優先級決定應調度哪個線程來執行。

線程的優先級用數字表示,從範圍1到10

  • Thread.MIN_PRIOPITY = 1
  • Thread.MAX_PRIOPITY = 10
  • Thread.NORM_PRIORITY = 5

使用下述方法獲得或設置線程對象的優先級。

  • int getPriority();
  • void setPriority(int newPriority);

優先級的設定建議在start()調用前

注意:優先級低只是意味着獲得調度的概率低。並不是絕對先調用優先級高後調用優先級低的線程。

守護線程
  • 線程分爲用戶線程和守護線程
  • 虛擬機必須確保用戶線程執行完畢
  • 虛擬機不用等待守護線程執行完畢
  • 如後臺記錄操作日誌、監控內存使用等。
new Thread(god).setDaemon(true);//將用戶線程調整爲守護線程
常用其他方法
方法 功能
isAlive() 判斷線程是否還活着,即線程是否還未終止
setName() 給線程起一個名字
getName() 獲取線程的名字
currentThread() 取得當前正在運行的線程對象,也就是獲取自己本身

線程同步(synchronized)併發控制

併發:

同一個對象多個線程同時操作

處理多線程問題時,多個線程訪問同一對象,並且某些線程還想修改這個對象。這時候,我們就需要用到“線程同步”。線程同步其實就是一種等待機制,多個需要同時訪問此對象的線程進入這個對象的等待池形成隊列,等待前面的線程使用完畢後,下一個線程再使用。

線程同步

由於同一進程的多個線程共享同一塊存儲空間,在帶來方便的同時,也帶來了訪問衝突的問題。爲了保證數據在方法中被訪問時的正確性,在訪問時加入 鎖機制(synchronized),當一個線程獲得對象的排它鎖,獨佔資源,其他線程必須等待,使用後釋放鎖即可。存在以下問題:

  • 一個線程持有鎖會導致其他所有需要此鎖的線程掛起
  • 在多線程競爭下,枷鎖、釋放鎖會導致比較多的上下文切換和調度延時,因其性能問題。
  • 如果一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先級倒置,引起性能問題。

由於我們可以通過private關鍵字來保證數據對象只能被方法訪問,所以我們只需要針對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩種用法:synchronized方法和synchronized塊。

  • 同步方法

    public synchronized void method(int args){}

synchronized方法控制對“成員變量|類變量”對象的訪問:每個對象對應一把鎖,每個synchronized方法都必須獲得調用該方法的對象的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀態。

缺陷:若將一個大的方法聲明爲synchronized將會大大影響效率。

  • 同步塊:synchronized(obj){},obj稱之爲同步監視器
    • obj可以是任何對象,但是推薦使用共享資源作爲同步監視器
    • 同步方法中無需指定同步監視器,因爲同步方法的同步監視器是this即該對象本身,或class即類的模子
package com.junwei.syn;

/**
 * @Auther: wangjunwei
 * @Description: 線程安全:在併發時保證數據的正確性,效率儘可能高
 * synchronized
 * 1、同步方法
 * 2、同步塊
 * @Date: Created in 19:02 2018/12/26
 */
public class SynTest01 {
    public static void main(String[] args) {
        //一份資源
        SafeWeb12306 web = new SafeWeb12306();
        //多個代理
        new Thread(web, "黃牛1").start();
        new Thread(web, "黃牛2").start();
        new Thread(web, "黃牛3").start();
    }
}

class SafeWeb12306 implements Runnable {
    /*總票數*/
    private int ticketNums = 99;
    private boolean flag = true;

    /**
     * @Author: wangjunwei
     * @Date: Created in 12:42 2018/12/26
     * @Description: 重寫run方法
     */

    @Override
    public void run() {
        while (flag) {
            test();
        }
    }
     /**
      * @Author: wangjunwei
      * @Date: Created in 19:12 2018/12/26
      * @Description: 線程安全
      */

    public synchronized void test() {
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        /*獲取當前線程名稱 並打印剩餘票數*/
        System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
    }

}

  • 同步監視器的執行過程
    • 第一個線程訪問,鎖定同步監視器,執行其中代碼
    • 第二個線程訪問,發現同步監視器被鎖定,無法訪問
    • 第一個線程訪問完畢,解鎖同步監視器
    • 第二個線程訪問,發現同步監視器未鎖,鎖定並訪問
package com.junwei.syn;

import java.util.ArrayList;
import java.util.List;

/**
 * @Auther: wangjunwei
 * @Description: 操作容器
 * @Date: Created in 19:36 2018/12/26
 */
public class SynBlockTest {
    public static void main(String[] args) throws InterruptedException {
        final List<String> list = new ArrayList<String>();
        for (int i=0;i<10000;i++){
            new Thread(()->{
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }}).start();
        }
        Thread.sleep(5000);
        System.out.println(list.size());
    }
}

性能分析(多用同步塊)

package com.junwei.syn;

/**
 * @Auther: wangjunwei
 * @Description:
 * @Date: Created in 20:09 2018/12/26
 */
public class SynBlockTest03 {
    public static void main(String[] args) {
        //一份資源
        SynSafeWeb12306 web = new SynSafeWeb12306();
        //多個代理
        new Thread(web, "黃牛1").start();
        new Thread(web, "黃牛2").start();
        new Thread(web, "黃牛3").start();
    }
}

class SynSafeWeb12306 implements Runnable {
    /*總票數*/
    private int ticketNums = 99;
    private boolean flag = true;

    /**
     * @Author: wangjunwei
     * @Date: Created in 12:42 2018/12/26
     * @Description: 重寫run方法
     */

    @Override
    public void run() {
        while (flag) {
            test();
        }
    }

    /**
     * @Author: wangjunwei
     * @Date: Created in 19:12 2018/12/26
     * @Description: 線程安全
     * 雙重檢測 double checking
     */

    public void test() {
        //考慮沒票的情況
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        //考慮只有一張票的情況
        synchronized (this) {
            if (ticketNums <= 0) {
                flag = false;
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            /*獲取當前線程名稱 並打印剩餘票數*/
            System.out.println(Thread.currentThread().getName() + "-->" + ticketNums--);
        }
    }

}
影院購票案例
package com.junwei.syn;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @Auther: wangjunwei
 * @Description: 快樂影院
 * @Date: Created in 20:25 2018/12/26
 */
public class HappyCinema {
    public static void main(String[] args) throws InterruptedException {
        Cinema cinema = new Cinema(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), "《喜洋洋與灰太狼》");
        new Thread(new Customer(cinema, Arrays.asList(1, 3, 7, 9)), "顧客1").start();
        new Thread(new Customer(cinema, Arrays.asList(2, 4, 6, 8, 10)), "顧客2").start();
        new Thread(new Customer(cinema, Arrays.asList(5, 8)), "顧客3").start();
    }
}

/**
 * 顧客
 */
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
class Customer implements Runnable {
    private Cinema cinema;
    private List<Integer> seats;

    @Override
    public void run() {
        synchronized (cinema) {
            //調用影院方法 查看票是否夠
            boolean flag = cinema.bookTickets(seats);
            //如果票夠 提示購票成功
            if (flag) {
                System.out.println("出票成功!" + Thread.currentThread().getName() + "->位置爲:" + seats);
            } else {
                System.out.println("出票失敗!" + Thread.currentThread().getName() + "->位置不夠!");
            }
        }
    }
}

/**
 * 影院
 */
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
class Cinema {
    /**
     * 可用的位置
     */
    private List<Integer> available;
    /**
     * 名稱
     */
    private String name;

    /**
     * @param seats 1 要選幾個位子
     * @return : boolean
     * @Author: wangjunwei
     * @Date: Created in 20:29 2018/12/26
     * @Description: 判斷是否購票成功
     */

    public boolean bookTickets(List<Integer> seats) {
        //輸出剩餘位置
        System.out.println("可用位置爲:" + available);
        //臨時存放集合
        List<Integer> copy = new ArrayList<>();
        copy.addAll(available);
        //相減
        copy.removeAll(seats);
        //判斷大小
        if (available.size() - copy.size() == seats.size()) {
            //如果成功 將票的庫存重新賦值
            available = copy;
            return true;
        }
        return false;
    }

}

/**運行結果*/
可用位置爲:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
出票成功!顧客1->位置爲:[1, 3, 7, 9]
可用位置爲:[2, 4, 5, 6, 8, 10]
出票成功!顧客2->位置爲:[2, 4, 6, 8, 10]
可用位置爲:[5]
出票失敗!顧客3->位置不夠!

併發容器

**java.util.concurrent.CopyOnWriteArrayList **

CopyOnWriteArrayList內部已經實現同步,則不需要在方法裏進行同步

死鎖

多個線程各自佔有一些共享資源,並且互相等待其他線程佔有的資源才能進行,而導致兩個或多個線程都在等待對方釋放資源,都停止執行的情景。某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能會發生“死鎖”的問題。

避免:不要在同一個代碼塊中同時持有多個鎖

package com.junwei.syn;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * @Auther: wangjunwei
 * @Description: 死鎖
 * 過多的同步可能造成互相不釋放資源
 * 從而互相等待,一般發生於同步中持有多個對象的鎖
 * @Date: Created in 21:52 2018/12/26
 */
public class DeadLock {
    public static void main(String[] args) {
        Markup m1 = new Markup(1,"丫頭");
        Markup m2 = new Markup(2,"公主");
        m1.start();
        m2.start();
    }
}

/**
 * @Author: wangjunwei
 * @Date: Created in 21:55 2018/12/26
 * @Description: 口紅
 */

class Liostick {

}

/**
 * @Author: wangjunwei
 * @Date: Created in 21:55 2018/12/26
 * @Description: 鏡子
 */

class Mirror {

}

/**
 * @Author: wangjunwei
 * @Date: Created in 21:54 2018/12/26
 * @Description: 化妝
 */
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
class Markup extends Thread {
    static Liostick li = new Liostick();
    static Mirror mi = new Mirror();
    /**
     * 選擇
     */
    private int choic;
    /**
     * 名字
     */
    private String girl;

    @Override
    public void run() {
        //化妝
        makeUp();
    }
     /**
      * @Author: wangjunwei
      * @Date: Created in 21:58 2018/12/26
      * @Description: 相互持有對方的對象鎖
      * 可能造成死鎖
      */

    private void makeUp(){
        if (choic==0){
            synchronized (li){
                //獲得口紅的鎖
                System.out.println(this.getGirl()+"獲得口紅");
                //1秒後想擁有鏡子
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (mi){
                //獲得鏡子
                System.out.println(this.getGirl()+"獲得鏡子");
            }
        }else {
            synchronized (mi){
                //獲得口紅的鎖
                System.out.println(this.getGirl()+"獲得鏡子");
                //2秒後想擁有鏡子
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (li){
                //獲得鏡子
                System.out.println(this.getGirl()+"獲得口紅");
            }
        }
    }
}

生產者消費者

線程協作(線程通信)

分析:這是一個線程同步問題,生產者和消費者共享一個資源,並且生產者和消費者之間相互依賴,互爲條件

  • 對於生產者,沒有生產產品之前,要通知消費者等待。而生產了產品之後,又需要馬上通知消費者消費。
  • 對於消費者,在消費之後,要通知生產者已經消費結束,需要繼續生產新的產品以供消費
  • 在生產者消費者問題中,僅有synchronized是不夠的
    • synchronized可阻止併發更新同一個共享資源,實現了同步
    • synchronized不能用來實現不同線程之間的消息傳遞(通信 )
解決方式1:

併發協作模型“生產者/消費者模式”–>管程法

  • 生產者:負責生產數據的模塊(這裏模塊可能是:方法、對象、線程、進程)
  • 消費者: 負責處理數據的模塊(這裏模塊可能是:方法、對象、線程、進程)
  • 緩衝區:消費者不能直接使用生產者的數據,它們之間有個“緩衝區”

生產者將生產好的數據放入“緩衝區”,消費者從“緩衝區”拿要處理的數據

package com.junwei.cooperation;

import lombok.AllArgsConstructor;

/**
 * @Auther: wangjunwei
 * @Description: 協作模型
 * 生產者消費者實現方式一:管程法
 * @Date: Created in 09:06 2018/12/27
 */
public class CoTest01 {
    public static void main(String[] args) {
        //緩衝區
        SynContainer sc = new SynContainer();
        new Productor(sc).start();
        new Consumer(sc).start();
    }
}

/**
 * @Author: wangjunwei
 * @Date: Created in 9:07 2018/12/27
 * @Description: 生產者
 */
@AllArgsConstructor
class Productor extends Thread {
    /**
     * 緩衝區
     */
    SynContainer container;

    @Override
    public void run() {
        //生產
        for (int i = 0; i < 100; i++) {
            System.out.println("生產-->" + i + "個產品");
            //調用方法生產
            container.push(new Steamedbun(i));
        }
    }
}

/**
 * @Author: wangjunwei
 * @Date: Created in 9:07 2018/12/27
 * @Description: 消費者
 */
@AllArgsConstructor
class Consumer extends Thread {
    /**
     * 緩衝區
     */
    SynContainer container;

    @Override
    public void run() {
        //消費
        for (int i = 0; i < 100; i++) {
            //調用方法消費
            System.out.println("消費-->" + container.pop().id + "個產品");
        }
    }
}

/**
 * @Author: wangjunwei
 * @Date: Created in 9:07 2018/12/27
 * @Description: 緩衝區
 */
class SynContainer {
    /**
     * 產品存儲容器
     */
    Steamedbun[] buns = new Steamedbun[10];
    /**
     * 計數器
     */
    int count = 0;

    /**
     * @param bun 1
     * @return : void
     * @Author: wangjunwei
     * @Date: Created in 9:12 2018/12/27
     * @Description: 存儲 生產
     */

    public synchronized void push(Steamedbun bun) {
        //何時能生產 容器存在空間
        //不能生產 只有等待
        if (count==buns.length){
            try {
                //線程阻塞 消費者通知 生產解除
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        buns[count] = bun;
        count++;
        //存在數據了 可以通知消費了
        this.notifyAll();
    }

    /**
     * @return : com.junwei.cooperation.Steamedbun
     * @Author: wangjunwei
     * @Date: Created in 9:14 2018/12/27
     * @Description: 獲取 消費
     */

    public synchronized Steamedbun pop() {
        //何時消費 容器中是否存在數據
        //沒有數據 只有等待
        if (count==0){
            try {
                //此時線程阻塞 生產者通知消費 阻塞解除
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        Steamedbun bun = buns[count];
        //存在空間了 可以喚醒生產了
        this.notifyAll();
        return bun;
    }
}

/**
 * @Author: wangjunwei
 * @Date: Created in 9:07 2018/12/27
 * @Description: 數據
 */
@AllArgsConstructor
class Steamedbun {
    int id;
}
解決方式2:

併發協作模型“生產者/消費者模式”–>信號燈法

Java提供了3個方法解決線程之間的通信問題

package com.junwei.cooperation;

import lombok.AllArgsConstructor;

/**
 * @Auther: wangjunwei
 * @Description: 協作模型:
 * 生產者消費者實現方式二:信號燈法
 * 藉助標誌位
 * @Date: Created in 09:43 2018/12/27
 */
public class CoTest02 {
    public static void main(String[] args) {
        Tv tv = new Tv();
        new Actor(tv).start();
        new Dudience(tv).start();
    }
}

/**
 * @Author: wangjunwei
 * @Date: Created in 9:45 2018/12/27
 * Description: 演員 生產者 繼承線程類
 */
@AllArgsConstructor
class Actor extends Thread {
    Tv tv;

    /**
     * @Author: wangjunwei
     * @Date: Created in 9:46 2018/12/27
     * @Description: 重寫run方法
     */

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                this.tv.play("動物世界");
            } else {
                this.tv.play("廣告時間");
            }
        }
    }
}

/**
 * @Author: wangjunwei
 * @Date: Created in 9:47 2018/12/27
 * @Description: 觀衆 消費者 繼承線程類
 */
@AllArgsConstructor
class Dudience extends Thread {
    Tv tv;

    /**
     * @Author: wangjunwei
     * @Date: Created in 9:47 2018/12/27
     * @Description: 重寫 run方法
     */

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            this.tv.watch();
        }
    }
}

/**
 * @Author: wangjunwei
 * @Date: Created in 9:48 2018/12/27
 * @Description: 同一個資源 電視
 */
class Tv {
    /**
     * 聲音 資源
     */
    String voice;
    /**
     * 信號燈
     * T 表示演員表演 觀衆等待
     * F 表示觀衆觀看 演員等待
     */
    boolean flag = true;


    /**
     * @param voice 1
     * @Author: wangjunwei
     * @Date: Created in 9:51 2018/12/27
     * @Description: 表演
     */

    public synchronized void play(String voice) {
        //演員等待
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("表演了: " + voice);
        this.voice = voice;
        this.notifyAll();
        //切換標誌
        this.flag = !this.flag;
    }

    /**
     * @Author: wangjunwei
     * @Date: Created in 9:53 2018/12/27
     * @Description: 觀衆觀看
     */

    public synchronized void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //觀衆等待
        System.out.println("聽到了:" + voice);
        this.notifyAll();
        //切換標誌
        this.flag = !this.flag;
    }
}


方法名 作用
final void wait() 表示線程一直等待,直到其他線程通知,與sleep不同,會釋放鎖
final void wait(long timeout) 指定等待的毫秒數
final void notifiy() 喚醒一個處於等待狀態的線程
final void notifyAll() 喚醒同一個對象上所有調用wait()方法的線程,優先級別高的線程優先調度

均是java.lang.Object類的方法都只能在同步方法或者同步代碼塊中使用,否則會拋出異常

高級主題

任務定時調度

通過Timer和Timerask,我們可以實現定時啓動某個線程

  • java.util.Timer:類似鬧鐘的功能,本身實現的就是一個線程
  • java.util.TimerTask:一個抽象類,該類實現了Runnable接口,所以該類具備多線程的能力。
package com.junwei.others;

import org.junit.Test;

import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @Auther: wangjunwei
 * @Description: 任務調度
 * Timer 和 TimerTask
 * @Date: Created in 10:17 2018/12/27
 */
public class TimerTest01 {
    public static void main(String[] args) {
        //執行安排
        Timer timer = new Timer();
        //執行任務 參數 第一次執行時間 
        timer.schedule(new MyTask(), 1000, 1000);
    }

}

class MyTask extends TimerTask {
    @Override
    public void run() {
        //定時任務
        System.out.println(new SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis()));
        System.out.println("-----------------");
    }

}

Quartz(任務調度框架)

  1. Scheduler:調度器,控制所有的調度
  2. Trigger:觸發條件,採用DSL模式
  3. JobDetail:需要處理的JOB
  4. Job:執行邏輯

DSL:Domain-specific language領域特定語言,針對一個特定的領域,具有受限表達性的一種計算機程序語言,即領域專用語言,聲明式編程:

  • Method Chaining 方法鏈 Fluent Style流暢風格,builder模式構建器
  • Nested Functions 嵌套函數
  • Lambda Expressions/Closures
  • Functional Sequence

HappenBefore

  • 你寫的代碼很可能根本沒按照你期望的順序執行,因爲編譯器和cpu會嘗試重排指令使得代碼更快運行

執行代碼的順序可能與編寫代碼不一致,即虛擬機優化代碼順序,則爲指令重排

happen-before即:編譯器或運行時環境爲了優化程序性能而採取的對指令進行重新排序執行的一種手段。

Volatile(不常見)

volatile保證線程間變量的可見性,簡單地說就是當線程A對變量X進行了修改後,在線程A後面執行的其他線程能看到變量X的變動,更詳細地說是要符合以下兩個規則:

  • 線程對變量進行修改之後,要立刻回寫到主存
  • 線程對變量讀取的時候,要從主存中讀。而不是緩存

各線程的工作內存間彼此獨立、互不可見,在線程啓動的時候,虛擬機爲每個內存分配一塊工作內存,不僅包括了線程內部定義的局部變量,也包含了線程所需要使用的共享變量(非線程內構造的對象)的副本,即爲了提高執行效率。

volatile是不錯的機制,但是volatile不能保證原子性。

CAS

鎖分爲兩類:

  • 悲觀鎖:synchronized是獨佔鎖即悲觀鎖,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。
  • 樂觀鎖:每次不加鎖而是假設沒有衝突而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止。
Compare and Swap 比較並交換:
  • 樂觀鎖的實現:
  • 有三個值:一個當前內存值V、舊的預期值A、將更新的值B。現獲取到內存當中當前的內存值V,再將內存值V和原值A作比較,要是相等就修改爲要修改的值B並返回true。否則什麼都不做,並返回false
  • CAS是一組原子操作,不會被外部打斷
  • 屬於硬件級別的操作(利用cpu的CAS指令,同時藉助JNI來完成的非阻塞算法),效率比枷鎖操作高
  • ABA問題:如果變量V初次讀取的時候是A,並且在準備賦值的時候檢查到它仍然是A,那能說明它的值沒有被其他線程修改過了嗎?如果在這段時間曾經被改成B,然後又改回A,那CAS操作就會無人爲它從來沒有被修改過。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章