1. 程式人生 > 實用技巧 >多執行緒學習

多執行緒學習

核心概念

  • 執行緒就是獨立的執行路徑

  • Main()稱之為主執行緒,為系統的入口,用於執行整個程式

  • 在一個程序中,如果開闢了多個執行緒,執行緒的執行有排程器安排排程,排程器是與作業系統緊密相關的,先後順序是不能人為干預的

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

  • 執行緒會帶來額外的開銷,如cpu排程時間,併發控制開銷

  • 每個執行緒在自己的工作記憶體互動,記憶體控制不當會造成資料不一致


Demo1 簡單理解執行緒

public class Demo1 extends Thread {
    @Override
    public void run() {
        //super.run();
        for (int i = 0; i < 20; i++) {
            System.out.println(" 子執行緒在跑 "+ i);
        }

    }

    public static void main(String[] args) {
        Demo1 demo1 =new Demo1();
        //demo1.run();//普通的執行run,正常順序
        demo1.start();//建立子執行緒,執行run,主執行緒和子執行緒的執行,由CPU進行排程,無法人為干預
        //
        for (int i = 0; i < 100; i++) {
            System.out.println("主執行緒在跑"+i);
        }
    }
}
/*
主執行緒在跑0
 子執行緒在跑 0
 子執行緒在跑 1
 子執行緒在跑 2
 子執行緒在跑 3
 子執行緒在跑 4
主執行緒在跑1
主執行緒在跑2
主執行緒在跑3
主執行緒在跑4
主執行緒在跑5
主執行緒在跑6
主執行緒在跑7
主執行緒在跑8
主執行緒在跑9
主執行緒在跑10
主執行緒在跑11
主執行緒在跑12
主執行緒在跑13
主執行緒在跑14
主執行緒在跑15
主執行緒在跑16
主執行緒在跑17
 子執行緒在跑 5
 子執行緒在跑 6
主執行緒在跑18
主執行緒在跑19
 子執行緒在跑 7
 子執行緒在跑 8
 子執行緒在跑 9
 子執行緒在跑 10
 子執行緒在跑 11
 子執行緒在跑 12
 子執行緒在跑 13
 子執行緒在跑 14
 子執行緒在跑 15
 子執行緒在跑 16
 子執行緒在跑 17
 子執行緒在跑 18
 子執行緒在跑 19
主執行緒在跑20
主執行緒在跑21
*/

Demo2 下載網圖

import org.apache.commons.io.FileUtils;

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

public class Demo2 {
    int k=0;
    public static void main(String[] args) {
        WebDownloader wd =new WebDownloader("https://i0.hdslb.com/bfs/album/92033a2dfd1630bf3d013965e1769258ee88eb35.png","1.png");
        WebDownloader wd2 =new WebDownloader("https://i0.hdslb.com/bfs/album/cebd6f76c2317c472e6a0a5610fea0882931cbc3.jpg","2.jpg");
        wd.start();
        wd2.start();
        /*
        下載了:1.png
        下載了:2.jpg
         */
    }
}
class WebDownloader extends Thread{
    private String Url;
    private String ServerFileName;
    public WebDownloader(String url,String serverFileName)
    {
        this.Url = url;
        this.ServerFileName=serverFileName;
    }

    @Override
    public void run() {
        //super.run();
        try {
            //copyURLToFile 方法丟擲了異常,上層函式一定要處理這個異常
            FileUtils.copyURLToFile(new URL(this.Url), new File(this.ServerFileName));
            System.out.println("下載了:"+this.ServerFileName);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("下載圖片報錯");
        }
    }
}

Demo3 使用Runable介面實現多執行緒

public class Demo3 implements Runnable {

    @Override
    public void run() {
        //super.run();
        for (int i = 0; i < 20; i++) {
            System.out.println(" 子執行緒在跑 "+ i);
        }
    }

    public static void main(String[] args) {
        Demo3 demo3 =new Demo3();
        new Thread(demo3).start();//呼叫方式稍作改變
        //
        for (int i = 0; i < 100; i++) {
            System.out.println("主執行緒在跑"+i);
        }
    }
}

Demo4 使用Callable的介面實現多執行緒

public class Demo5  {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        WebDownloader2 wd =new WebDownloader2("https://i0.hdslb.com/bfs/album/92033a2dfd1630bf3d013965e1769258ee88eb35.png","1.png");
        WebDownloader2 wd2 =new WebDownloader2("https://i0.hdslb.com/bfs/album/cebd6f76c2317c472e6a0a5610fea0882931cbc3.jpg","2.jpg");
        ExecutorService service = Executors.newFixedThreadPool(2);
        Future<Boolean> res1 = service.submit(wd);
        Future<Boolean> res2 = service.submit(wd2);
        Boolean r1 = res1.get();
        Boolean r2 = res2.get();
        System.out.println(r1);
        System.out.println(r2);
    }
}
class WebDownloader2 implements Callable<Boolean> {
    private String Url;
    private String ServerFileName;
    public WebDownloader2(String url,String serverFileName)
    {
        this.Url = url;
        this.ServerFileName=serverFileName;
    }
    @Override
    public Boolean call() {
        //super.run();
        try {
            //copyURLToFile 方法丟擲了異常,上層函式一定要處理這個異常
            FileUtils.copyURLToFile(new URL(this.Url), new File(this.ServerFileName));
            System.out.println("下載了:"+this.ServerFileName);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("下載圖片報錯");
        }
        return true;
    }

}

靜態代理模式

Thread 實現物件執行方法的模式

//總結:
//真是執行物件和代理物件都要整合同一個介面
//代理物件要代理真實角色
//好處
//1.代理物件可以做很多真實物件做不了的事,或者說不好做的事
//2.真實物件專注自己的業務
public class Demo6 {
    public static void main(String[] args) {
        Person person = new Person();
        //靜態代理類傳入 被代理物件
        MarryCompany marryCompany = new MarryCompany(person);
        marryCompany.HappyMarry();//靜態代理類,幫助被代理物件執行
        //Tread 類實現新執行緒執行物件方法就是靜態代理模式
        new Thread(()-> System.out.println("Tread 類實現新執行緒執行物件方法就是靜態代理模式")).start();
    }
}

/**
 * Marry介面
 */
interface Marry
{
    void HappyMarry();
}

/**
 * Person類,靜態代理實際執行的物件類
 */
class Person implements Marry
{
    @Override
    public void HappyMarry() {
        System.out.println("結婚啦");
    }
}
/**
* MarryCompany Marry代理類
 */
class MarryCompany implements Marry
{
    private Marry _marryPerson;
    public MarryCompany(Marry marryPerson)
    {
        this._marryPerson =marryPerson;
    }
    @Override
    public void HappyMarry() {
        System.out.println("收定金,佈置會場");
        this._marryPerson.HappyMarry();
        System.out.println("收尾款");
    }
}

執行緒狀態

五種狀態

建立狀態 就緒狀態 阻塞狀態 執行狀態 死亡狀態

Sleep

Sleep阻塞執行緒,模擬網路延時可以放大問題的放生性。

public class Demo7 {
    public static void main(String[] args) throws InterruptedException {
        Date currentTime = new Date(System.currentTimeMillis());
        for (int i = 0; i < 10; i++) {
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(currentTime));//java操作時間和c#寫起來感覺還是有很大區別的
            Thread.sleep(1000);
            currentTime = new Date(System.currentTimeMillis());
        }
    }
}

執行緒禮讓 yeild

讓CPU重新排程,禮讓不一定成功,看CPU排程

public class Demo8 {
    public static void main(String[] args) {
        TestYeild testYeild1 = new TestYeild();
        TestYeild testYeild2 = new TestYeild();
        new Thread(testYeild1,"a").start();
        new Thread(testYeild2,"b").start();
    }
}
class TestYeild implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"開始執行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"結束執行");
    }
}

Join

Join合併執行緒,待此執行緒執行完成之後,在執行其他執行緒

public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        for (int i = 0; i < 100; i++) {
            if(i==0)
            {
                thread.start();
            }
            if(i==10)
            {
                System.out.println("開始插隊=================");
                thread.join();
            }
            System.out.println("主執行緒"+i);
        }
    }
}
class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("插隊執行緒"+i);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("插隊還不快點,還睡覺==============");
    }
}
/*
主執行緒0
主執行緒1
主執行緒2
主執行緒3
主執行緒4
主執行緒5
主執行緒6
主執行緒7
主執行緒8
插隊執行緒0
主執行緒9
開始插隊=================
插隊執行緒1
插隊執行緒2
插隊執行緒3
插隊執行緒4
插隊執行緒5
插隊執行緒6
....
插隊執行緒43
插隊執行緒44
插隊執行緒45
插隊執行緒46
插隊執行緒47
插隊執行緒48
插隊執行緒49
插隊還不快點,還睡覺==============
主執行緒10
主執行緒11
主執行緒12
....
主執行緒88
主執行緒89
主執行緒90
主執行緒91
主執行緒92
主執行緒93
主執行緒94
主執行緒95
主執行緒96
主執行緒97
主執行緒98
主執行緒99

Process finished with exit code 0

*/

執行緒優先順序

執行緒優先順序高不一定先執行,但權重高!

min=1 max=10 normal=5

先設定優先順序再啟動

守護執行緒

執行緒分為使用者執行緒和守護執行緒。虛擬機器必須確保使用者執行緒執行完畢。虛擬機器不用等待守護執行緒執行完畢。如後臺記錄操作日誌,監控記憶體,垃圾回收等待。

public class Demo10 {
    public static void main(String[] args) {
        DeamonThread deamon = new DeamonThread();
        UserThread user = new UserThread();
        Thread deamonThread = new Thread(deamon);
        deamonThread.setDaemon(true);//守護執行緒預設為false
        deamonThread.start();
        Thread userThread = new Thread(user);
        userThread.start();
    }
}
class UserThread implements Runnable
{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("使用者執行緒執行第"+i);
        }
    }
}
class DeamonThread implements Runnable
{
    @Override
    public void run() {
        //當主執行緒和其他使用者執行緒執行完畢之後,守護執行緒也隨之關閉
        while(true){
            System.out.println("守護執行緒執行中");
        }
    }
}

同步方法和同步快

public class Demo4 implements Runnable {
    int ticket = 10;
    @Override
    public synchronized void run() {//鎖住run方法
        while (ticket>0)
        {
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"張票");
        }
    }

    public static void main(String[] args) {
        Demo4 demo4 = new Demo4();
        new Thread(demo4,"小明").start();
        new Thread(demo4,"小紅").start();
        new Thread(demo4,"小藍").start();
    }
}
import java.util.ArrayList;
import java.util.List;

public class Demo11 {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();//這裡例項化跟C#不太一樣
        for (int i = 0; i < 20000; i++) {
            new Thread(()->{
                synchronized (list)
                {
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        Thread.sleep(6000);
        System.out.println(list.size());//不加 synchronized的情況下 19997!=20000,add當前物件的相同位置,覆蓋了
    }
}
public class Demo12 {
    public static void main(String[] args) {
        Account account = new Account("共同賬戶",100);
        Withdraw withdraw = new Withdraw(account,100);
        new Thread(withdraw).start();
        new Thread(withdraw).start();
        new Thread(withdraw).start();
        new Thread(withdraw).start();
    }
}

class Account{
    String name;
    int money;
    public Account(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

class Withdraw extends Thread
{
    Account account;
    int withdraw;
    int nowMoney;
    public Withdraw(Account account, int withdraw) {
        this.account = account;
        this.withdraw = withdraw;
    }

    @Override
    public void run()
    {
        try {
            sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (account)
        {
            if(account.money>0 && account.money>=withdraw)
            {
                int rs = this.account.money-this.withdraw;
                this.account.money -=this.withdraw;
                this.nowMoney += this.withdraw;
                System.out.println("餘額"+rs+";現在手中的錢有:"+nowMoney);
            }
            else
            {
                System.out.println("餘額不足");
            }
        }
    }
}
import java.util.concurrent.CopyOnWriteArrayList;

public class Demo13 {
    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();//這裡例項化跟C#不太一樣
        for (int i = 0; i < 20000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        Thread.sleep(6000);
        System.out.println(list.size());//CopyOnWriteArrayList 執行緒安全類
    }
}

生產者消費者問題

解決方案一:

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

生產者:負責生產資料的模組

消費者:負責處理資料的模組

緩衝區:消費者不能直接使用生產者的資料,消費者從緩衝區拿出資料

解決方案二:

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

通過標誌位,判斷是否可以通行

public class Demo14 {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();
        Cook cook =new Cook(synContainer);
        Custom custom = new Custom(synContainer);
        new Thread(cook,"cook").start();
        new Thread(custom,"Custom").start();

    }
}
//產品 雞
class Chicken
{
    public int id;

    public Chicken(int id) {
        this.id = id;
    }
}
//生產者
class Cook implements Runnable
{
    SynContainer synContainer;
    public Cook(SynContainer synContainer)
    {
        this.synContainer = synContainer;
    }

    @Override
    public void run() {
        // 生產者生產 產品
        for (int i = 0; i < 100; i++) {
            Chicken chicken = new Chicken(i);
            this.synContainer.push(chicken);
            System.out.println("生產者生產了第"+i+"只雞");
        }
    }

}
//消費者 顧客
class Custom implements Runnable
{
    SynContainer synContainer;
    public Custom(SynContainer synContainer)
    {
        this.synContainer = synContainer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            Chicken chicken = this.synContainer.pop();
            System.out.println("消費者消費了第"+chicken.id+"只雞");
        }
    }
}

class SynContainer
{
    Chicken[] chickens = new Chicken[10];
    int count = 0;

    public synchronized void push(Chicken chicken)
    {
        //容器滿了,等待生產者消費
        if(chickens.length == count)
        {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.chickens[count]=chicken;
        count++;
        //通知消費者,可以消費了
        this.notifyAll();
    }

    public synchronized Chicken pop() {
        if (count == 0) {
            //等待生產者生產
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Chicken chicken = this.chickens[count-1];
        //this.chickens[count] = null;
        count--;
        this.notifyAll();
        return chicken;
    }
}

notify和notifyAll的差別:notify只喚醒一個執行緒,且不保證能那個執行緒會被喚醒,這取決於執行緒排程器。notifyAll會讓等待該鎖的所有執行緒都會被喚醒。

執行緒池

思路:提前建立好多個執行緒池,放入執行緒池中,使用時直接獲取,使用完放回池中,可以避免頻繁建立銷燬、實現重複利用。類似生活中的公共交通工具。

好處

提高相應速度(減少了建立新執行緒的時間)

降低消耗資源(重複利用執行緒池中執行緒,不需要每次建立)

便於管理(核心池的大小(放多少執行緒)、最大執行緒數(同時跑多少執行緒)、執行緒沒有任務時最多保持多長時間)