1. 程式人生 > 實用技巧 >ES6的標準內建物件Proxy

ES6的標準內建物件Proxy

目錄

執行緒的建立

package com.linyh.demo01;

/**
 * @author lyh
 * @date 2020/10/29 14:42
 * 說明:
 */
public class ThreadDemo01 extends Thread{

    @Override
    public void run() {

        for (int i = 0; i < 200; i++) {
            System.out.println("子執行緒執行到----------" + i);
        }
    }

    public static void main(String[] args) {

        new ThreadDemo01().start();
        for (int i = 0; i < 5000; i++) {
            System.out.println("主執行緒執行到----------" + i);
        }
    }
}

  • 執行緒開啟代表加入就緒佇列,並不是立刻執行,cpu排程後才執行,此時還是主執行緒在執行,所以會出現上圖的現象

  • 先加入就緒佇列的執行緒也不一定先執行

  • 下面的寫法可以順序執行,但是這種寫法從始至終只有一個主執行緒:

    package com.linyh.demo01;
    
    /**
     * @author lyh
     * @date 2020/10/29 14:42
     * 說明:
     */
    public class ThreadDemo01 extends Thread{
    
        @Override
        public void run() {
    
            for (int i = 0; i < 200; i++) {
                System.out.println("子執行緒執行到----------" + i);
            }
        }
    
        public static void main(String[] args) {
    
            new ThreadDemo01().run();
            for (int i = 0; i < 5000; i++) {
                System.out.println("主執行緒執行到----------" + i);
            }
        }
    }
    

下載網路圖片

新建lib目錄,並從網路上下載commons-io-2.6.jar放入目錄,右擊lib點選:

點選專案結構,可以看到包匯入成功:

package com.linyh.demo01;

import org.apache.commons.io.FileUtils;

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

/**
 * @author lyh
 * @date 2020/10/29 15:09
 * 說明:
 */
public class ThreadDemo02 extends Thread{

    private String url;
    private String name;

    public ThreadDemo02(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public void run() {

        Downloader downloader = new Downloader();
        downloader.download(url, name);
        System.out.println("下載了" + name);
    }

    public static void main(String[] args) {

        new ThreadDemo02("https://www.luogu.com.cn/images/index/step1.png", "picture1.jpg").start();
        new ThreadDemo02("https://www.luogu.com.cn/images/index/step2.png", "picture2.jpg").start();
        new ThreadDemo02("https://www.luogu.com.cn/images/index/step3.png", "picture3.jpg").start();

    }
}


class Downloader {


    public void download(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            System.out.println("找不到該url");
        }
    }
}

可以看到,先加入就緒佇列的執行緒(picture1)也不一定先被cpu排程

實現Runnable介面建立執行緒

package com.linyh.demo01;

import org.apache.commons.io.FileUtils;

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

/**
 * @author lyh
 * @date 2020/10/29 15:55
 * 說明:
 */
public class RunnableDemo01 implements Runnable{
    private String url;
    private String name;

    public RunnableDemo01(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public void run() {

        Downloader downloader = new Downloader();
        downloader.download(url, name);
        System.out.println("下載了" + name);
    }

    public static void main(String[] args) {

        RunnableDemo01 t1 = new RunnableDemo01("https://www.luogu.com.cn/images/index/step1.png", "picture1.jpg");
        RunnableDemo01 t2 = new RunnableDemo01("https://www.luogu.com.cn/images/index/step2.png", "picture2.jpg");
        RunnableDemo01 t3 = new RunnableDemo01("https://www.luogu.com.cn/images/index/step3.png", "picture3.jpg");

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

多個執行緒同時操作同一個物件(重點)

package com.linyh.demo01;

/**
 * @author lyh
 * @date 2020/10/29 16:25
 * 說明:
 */
public class RunnableDemo02 implements Runnable{

    private int ticketNum = 10;
    @Override
    public void run() {

        while (true) {

            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "搶到了第" + ticketNum-- + "張票");

            if (ticketNum <= 0) {
                break;
            }

        }
    }

    public static void main(String[] args) {

        RunnableDemo02 runnableDemo02 = new RunnableDemo02();

        new Thread(runnableDemo02, "小明").start();
        new Thread(runnableDemo02, "小紅").start();
        new Thread(runnableDemo02, "小強").start();
    }
}

  • runnableDemo02物件是一個資源,該物件一建立就有10張票,它要做的就是從10張票中取票,使用Thread.currentThread().getName()可以知道該物件被哪個執行緒代理(哪個執行緒在取票);現在3個執行緒共同代理該物件。
  • 小紅執行緒訪問資源runnableDemo02物件中的ticketNum之後還未將票數減一就被小明物件訪問,便造成了該結果。
  • 多個執行緒訪問同一個資源,執行緒不安全。

龜兔賽跑

package com.linyh.demo01;

/**
 * @author lyh
 * @date 2020/10/29 17:11
 * 說明:該執行緒代表某個人在跑步,誰代理它就說明誰在做這件事(即誰在跑步)
 */
public class RunDemo implements Runnable{

    private static String winner = null;
    @Override
    public void run() {
        // 從0m跑到100m
        for (int i = 0; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "跑了" + i + "米");

            if (gameOver(i)) {
                return;
            }

            // 如果是兔子,每跑10米休息5ms
            if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }

    }

    // 跑了m米判斷遊戲是否結束
    private boolean gameOver(int m) {
        if (winner != null) {
            return true;
        }else {
            if (m >= 100) {
                winner = Thread.currentThread().getName();
                System.out.println("冠軍是" + Thread.currentThread().getName());
                return true;
            }else {
                return false;
            }
        }
    }

    public static void main(String[] args) {

        RunDemo runDemo = new RunDemo();

        new Thread(runDemo, "兔子").start();
        new Thread(runDemo, "烏龜").start();

    }
}

使用Callable實現圖片下載

  • Callable的好處

    • 呼叫物件時會有返回值
    • 呼叫異常會丟擲異常
  • 實現:

    package com.linyh.demo01;
    
    
    
    import java.util.concurrent.*;
    
    /**
     * @author lyh
     * @date 2020/10/29 19:34
     * 說明:
     */
    
    /*
    callable的好處:
    1.物件被呼叫時有返回值
    2.會丟擲異常
     */
    public class CallableDemo implements Callable<Boolean> {
    
        private String url;
        private String name;
    
        public CallableDemo(String url, String name) {
            this.url = url;
            this.name = name;
        }
    
        @Override
        public Boolean call() throws Exception {
            Downloader downloader = new Downloader();
            downloader.download(url, name);
            System.out.println("下載了" + name);
            return true;
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            CallableDemo c1 = new CallableDemo("https://www.luogu.com.cn/images/index/step1.png", "picture1.jpg");
            CallableDemo c2 = new CallableDemo("https://www.luogu.com.cn/images/index/step2.png", "picture2.jpg");
            CallableDemo c3 = new CallableDemo("https://www.luogu.com.cn/images/index/step3.png", "picture3.jpg");
    
            // 建立執行服務
            ExecutorService service = Executors.newFixedThreadPool(3);
    
    
            // 提交任務
            Future<Boolean> f1 = service.submit(c1);
            Future<Boolean> f2 = service.submit(c2);
            Future<Boolean> f3 = service.submit(c3);
    
            // 獲取結果
            System.out.println(f1.get());
            System.out.println(f2.get());
            System.out.println(f3.get());
    
            // 關閉服務
            service.shutdown();
        }
    }
    

靜態代理服務

package com.linyh.demo02;

/**
 * @author lyh
 * @date 2020/10/29 20:08
 * 說明:
 */
public class StaticProxy {

    public static void main(String[] args) {

        You you = new You();
        MarryProxy proxy = new MarryProxy(you);
        proxy.happyMarry();
    }
}

// 真實物件
class You implements Marry {

    @Override
    public void happyMarry() {
        System.out.println("你開心地結婚了!");
    }
}

// 代理物件
class MarryProxy implements Marry {

    // 代理物件
    private You you;

    public MarryProxy(You you) {
        this.you = you;
    }

    @Override
    public void happyMarry() {
        before();
        you.happyMarry();
        after();
    }

    private void after() {
        System.out.println("收尾款");
    }

    private void before() {
        System.out.println("佈置現場");
    }
}
interface Marry {

    public void happyMarry();
}

  • 真實物件和代理要實現同一個介面
  • 靜態代理一個真實物件對應一個代理
  • 代理角色代理真實物件做事,可是真正做事的還是真實角色(比如happyMarry)
  • 真實角色可以專注自己要做的事
  • 代理角色幫真實角色處理一些公共業務
  • 代理角色的公共業務可以拓展
  • 實現有效的分工

靜態代理和Runnable、Thread

  • 實現了Runnable介面的類(這裡記成A)專心做run時應該做的事情
  • Thread也實現了Runnable介面
  • Thread代理了A,所以Thread的run裡做的是A做的事情,同時還有一些公共業務,比如開啟執行緒,關閉執行緒等

Lamda表示式(首次出現在JDK1.8)

  • 函式式介面,也就是一個接口裡只有一個抽象方法的介面,比如Runnable:

    public interface Runnable {
        
        public abstract void run();
        
    }
    
  • 只有函式式介面可以轉成lamda表示式

  • 類寫法簡化歷程:

    外部類->靜態內部類/內部類->區域性內部類->匿名內部類->lamda表示式

  • Runnable的lamda寫法:

    Runnable runnable = ()->{
        System.out.println("這裡是Runnable的run方法主體");  
    };
    
    1. 建立一個匿名類實現Runnable介面,重寫run方法輸出一條語句
    2. 建立Runnable介面的runnable變數指向該匿名類

進一步簡化:

  • 帶引數的lambda表示式:

    package com.linyh.demo03;
    
    /**
     * @author lyh
     * @date 2020/10/29 21:56
     * 說明:
     */
    public class TestLambda {
    
    
        public static void main(String[] args) {
    
            new B().print(1);
        }
    }
    
    class B implements A {
        @Override
        public void print(int a) {
            System.out.println(a);
        }
    }
    interface A {
    
        void print(int a);
    }
    

    簡化(實現介面的類):

    package com.linyh.demo03;
    
    /**
     * @author lyh
     * @date 2020/10/29 21:56
     * 說明:
     */
    public class TestLambda {
    
    
        public static void main(String[] args) {
    
            A interfaceA = (int a)->{
                System.out.println(1);
            };
            interfaceA.print(1);
            
        }
    }
    
    
    interface A {
    
        void print(int a);
    }
    

    再簡化(引數所在括號、引數型別和括號):

    package com.linyh.demo03;
    
    /**
     * @author lyh
     * @date 2020/10/29 21:56
     * 說明:
     */
    public class TestLambda {
    
    
        public static void main(String[] args) {
    
            A interfaceA = a-> System.out.println(1);
            interfaceA.print(1);
    
    
        }
    }
    
    
    interface A {
    
        void print(int a);
    }
    

執行緒的停止

執行緒的停止應該滿足三個條件:

  1. 自己可以停止,比如執行緒中的迴圈到一定的次數會自己退出
  2. 可以呼叫執行緒的方法使迴圈退出(具體實現看等等的程式碼)
  3. 不要使用Thread裡自帶的stop、destory等方法,對於runnable物件來說,該方法也是代理幫助停止,不是自己停止!
package com.linyh.demo04;

/**
 * @author lyh
 * @date 2020/10/30 20:00
 * 說明:
 */
public class TestThreadStop {

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        new Thread(thread).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主執行緒輸出" + i);
            if (i == 500) {
                thread.stop();
                System.out.println("子執行緒結束了");
            }
        }
        System.out.println("主執行緒結束了");
    }
}


class MyThread implements Runnable{

    private boolean flag;
    @Override
    public void run() {

        flag = true;
        int i = 0;
        while (flag) {
            System.out.println("子執行緒輸出" + i++);
        }

    }

    public void stop() {
        flag = false;
    }
}

執行緒休眠

  1. 執行緒休眠(sleep)會使當前執行緒阻塞相應的時間(ms)
  2. 執行緒休眠會丟擲InterruptedException異常
  3. 執行緒休眠結束時會重新加入就緒佇列
  4. 可以模擬網路延時、倒計時(具體實現看下面程式碼)
  5. 每個物件都有一把鎖,執行緒休眠時不會釋放這把鎖

模擬網路延時,比如前面提到過的搶票:

package com.linyh.demo01;

/**
 * @author lyh
 * @date 2020/10/29 16:25
 * 說明:
 */
public class RunnableDemo02 implements Runnable{

    private int ticketNum = 10;
    @Override
    public void run() {

        while (true) {

            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "搶到了第" + ticketNum-- + "張票");

            if (ticketNum <= 0) {
                break;
            }

        }
    }

    public static void main(String[] args) {

        RunnableDemo02 runnableDemo02 = new RunnableDemo02();

        new Thread(runnableDemo02, "小明").start();
        new Thread(runnableDemo02, "小紅").start();
        new Thread(runnableDemo02, "小強").start();
    }
}

模擬網路延時可以放大問題發生性,什麼意思呢?就是使問題會發生的所有情況顯示出來。如果沒有延遲,就會發生下面這種情況:

這樣便體現不出“搶票“的特性了

模擬倒計時類似下面這樣:

package com.linyh.demo04;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author lyh
 * @date 2020/10/30 20:32
 * 說明:
 */
public class CountDown {

    public static void main(String[] args) {

        int i = 1000;

        while (i-- > 0) {
            Date date = new Date(System.currentTimeMillis());
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

執行緒讓步(yield)

讓步的含義是當前執行緒退出執行態,加入就緒佇列隊伍尾,由於之前我們驗證過執行緒的執行是沒有順序的,先加入就緒佇列的執行緒不一定先執行,所以cpu再次呼叫時還有可能呼叫該執行緒,那麼就禮讓失敗,否則禮讓成功

package com.linyh.demo05;

/**
 * @author lyh
 * @date 2020/10/30 20:57
 * 說明:
 */
public class YieldTest {

    public static void main(String[] args) {

        YieldDemo demo = new YieldDemo();

        new Thread(demo, "執行緒A").start();
        new Thread(demo, "執行緒B").start();
    }
}

class YieldDemo implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread() + "開始執行");
        Thread.yield();
        System.out.println(Thread.currentThread() + "執行結束");
    }
}

禮讓成功:

禮讓失敗:

執行緒插隊(join)

  1. 執行緒插隊後將其它執行緒阻塞直到當前執行緒結束

  2. 實現:

    package com.linyh.demo05;
    
    /**
     * @author lyh
     * @date 2020/10/30 21:11
     * 說明:
     */
    public class JoinDemo implements Runnable{
    
        @Override
        public void run() {
    
            for (int i = 0; i < 1000; i++) {
    
                System.out.println("執行緒vip的輸出" + i);
            }
            System.out.println("執行緒vip結束");
        }
    
        public static void main(String[] args) {
    
            JoinDemo demo = new JoinDemo();
            Thread thread = new Thread(demo);
            thread.start(); // 通常來說只是加入就緒佇列,當前還是主執行緒執行
            for (int i = 0; i <500; i++) {
    
                System.out.println("主執行緒的輸出" + i);
                if (i == 100) {
                    try {
                        thread.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("主執行緒結束");
        }
    }
    

執行緒的七大狀態

  1. 新生(new之後的狀態)
  2. 就緒(start之後的狀態)
  3. 執行(被cpu排程)
  4. 阻塞(得不到資源等待)
  5. 等待(和阻塞狀態不同,一個執行緒需要另一個執行緒執行特定動作後才能繼續,便進入該狀態,強調順序)
  6. 限時等待,5的基礎上時間到後就進入就緒狀態,或使用sleep
  7. 銷燬

執行緒一但銷燬(死亡)就不能再次啟動

觀測執行緒狀態

package com.linyh.demo05;

/**
 * @author lyh
 * @date 2020/10/30 21:47
 * 說明:
 */
public class ThreadStateDemo implements Runnable{

    @Override
    public void run() {

        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("執行緒執行結束");
    }

    public static void main(String[] args) {
        ThreadStateDemo demo = new ThreadStateDemo();
        Thread thread = new Thread(demo);

        Thread.State state = thread.getState();
        System.out.println(state);

        thread.start();
        state = thread.getState();
        System.out.println(state);

        while ((state = thread.getState()) != Thread.State.TERMINATED) {
            System.out.println(state);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

可以看出,sleep進入的是限時等待狀態

執行緒的優先順序

  • 優先順序高的只是被cpu呼叫的概率高,並不是高的一定先執行,有時候優先順序低的會先執行

  • 優先順序有上下界,出界會報異常:

  • 各個不同情況下的優先順序:

    package com.linyh.demo05;
    
    /**
     * @author lyh
     * @date 2020/10/30 22:18
     * 說明:
     */
    public class PriorityDemo implements Runnable{
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "執行緒優先順序" + Thread.currentThread().getPriority());
        }
    
    
        public static void main(String[] args) {
    
            PriorityDemo demo = new PriorityDemo();
            Thread thread1 = new Thread(demo, "A");
            Thread thread2 = new Thread(demo, "B");
            Thread thread3 = new Thread(demo, "C");
            Thread thread4 = new Thread(demo, "D");
    
            thread1.start();
    
            thread2.setPriority(Thread.MIN_PRIORITY);
            thread2.start();
    
            thread3.setPriority(Thread.NORM_PRIORITY);
            thread3.start();
    
            thread4.setPriority(Thread.MAX_PRIORITY);
            thread4.start();
        }
    }
    

    可以看到,如果沒設優先順序,那就是預設的5。

守護執行緒(daemon)(重點)

  • 執行緒分為守護執行緒和使用者執行緒
  • 虛擬機器要確保使用者執行緒結束後才會關閉
  • 虛擬機器不用確保守護執行緒執行結束就可關閉
  • 常見的守護執行緒:垃圾回收、記錄日誌、監測記憶體
package com.linyh.demo05;

/**
 * @author lyh
 * @date 2020/10/30 22:41
 * 說明:
 */
public class DeamonDemo {

    public static void main(String[] args) {

        Marx marx = new Marx();
        You you = new You();

        Thread thread1 = new Thread(marx);
        Thread thread2 = new Thread(you);

        // 設定為守護執行緒
        thread1.setDaemon(true);

        thread1.start();
        thread2.start();
    }
}


class Marx implements Runnable {

    @Override
    public void run() {

        while (true) {
            System.out.println("馬克思主義守護著你!");
        }
    }
}

class You implements Runnable {

    @Override
    public void run() {

        System.out.println("Hello World!");
        for (int i = 1; i < 36500; i++) {
            System.out.println("開心地活著!");
        }
        System.out.println("Goodbye World!");
    }
}

可以看到兩個執行緒交替執行,雖然守護執行緒可以不斷執行,但是虛擬機器確保thread2執行緒執行完畢,並且無需確保守護執行緒是否結束,所以虛擬機器關閉,那麼守護執行緒自然結束。

執行緒同步

  • 多個執行緒訪問同一個資源叫執行緒的併發

  • 多個執行緒如果要同時訪問同一個資源,要到該物件的等待佇列

  • 使用鎖+佇列機制實現執行緒同步

  • 一個程序中多個執行緒共用一個記憶體區域,如果沒有鎖會出問題

  • 一個執行緒訪問資源,如果該資源鎖還存在,那麼該執行緒拿到該排鎖,否則阻塞到等待佇列

  • 鎖機制會引起效能倒置問題,一般來說執行時間少的執行緒優先順序較高,如果優先順序低的執行緒拿到鎖,那麼便會導致執行時間短的執行緒等待執行時間長的執行緒先執行完

  • 鎖機制會造成較多的上下文切換,和排程延時

執行緒安全的2個例子

  1. 取錢
package com.linyh.syn;

import java.util.Date;

/**
 * @author lyh
 * @date 2020/10/31 8:59
 * 說明:
 */
public class UnsafeBank {

    public static void main(String[] args) {

        Account account = new Account("基金", 500);

        new Drawing(account, "小明", 200).start();
        new Drawing(account, "小紅", 500).start();
    }
}

class Account {
    private String name;
    private int money;

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

    public String getName() {
        return name;
    }

    public int getMoney() {
        return money;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setMoney(int money) {
        this.money = money;
    }
}


class Drawing extends Thread {

    private Account account;
    private int drawMoney;
    private String name;

    public Drawing(Account account, String name, int drawMoney) {
        super(name);
        this.account = account;
        this.name = name;
        this.drawMoney = drawMoney;
    }

    @Override
    public void run() {


        if (account.getMoney() - drawMoney < 0) {
            System.out.println(this.getName() + "無法取錢,賬戶餘額不足!");
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.getName() + "取了" + drawMoney);
        System.out.println(account.getName() + "卡內餘額為:" + (account.getMoney() - drawMoney));
        account.setMoney(account.getMoney() - drawMoney);

    }
}

出現該結果是因為小紅執行緒取錢時,卡內有500,然後小明執行緒取錢時,卡內餘額還沒來得及減500,當小明執行緒取完錢時,小紅執行緒已經取完錢,實際上是在0的基礎上減200

  1. ArrayList也是執行緒不安全的
package com.linyh.syn;

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

/**
 * @author lyh
 * @date 2020/10/31 9:41
 * 說明:
 */
public class ArrayListTest {

    public static void main(String[] args) {

        List<String> list = new ArrayList<>();

        for (int i = 0; i < 1000; i++) {

            // 建立一個執行緒,將該執行緒名字存入list
            new Thread(()->list.add(Thread.currentThread().getName())).start();

        }
        
        // 放大問題發生性
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

可以看到,size不為1000,原因是因為ArrayList是執行緒不安全的,當一個執行緒將名字存入,此時下標還沒來得及增加,另一個執行緒就被排程,那個執行緒將名字覆蓋在了之前那個執行緒存放的位置。

synchronize

synchronized修飾詞

  1. 該方法鎖的是this,所以下面這情況無效:
  1. synchronized程式碼塊,此時可以鎖任意物件,改成下面這樣便可實現同步:
package com.linyh.syn;

import java.util.Date;

/**
 * @author lyh
 * @date 2020/10/31 8:59
 * 說明:
 */
public class UnsafeBank {

    public static void main(String[] args) {

        Account account = new Account("基金", 500);

        new Drawing(account, "小明", 200).start();
        new Drawing(account, "小紅", 500).start();
    }
}

class Account {
    private String name;
    private int money;

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

    public String getName() {
        return name;
    }

    public int getMoney() {
        return money;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setMoney(int money) {
        this.money = money;
    }
}


class Drawing extends Thread {

    private Account account;
    private int drawMoney;
    private String name;

    public Drawing(Account account, String name, int drawMoney) {
        super(name);
        this.account = account;
        this.name = name;
        this.drawMoney = drawMoney;
    }

    @Override
    public void run() {

        synchronized (account) {
            if (account.getMoney() - drawMoney < 0) {
                System.out.println(this.getName() + "無法取錢,賬戶餘額不足!");
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.getName() + "取了" + drawMoney);
            System.out.println(account.getName() + "卡內餘額為:" + (account.getMoney() - drawMoney));
            account.setMoney(account.getMoney() - drawMoney);
        }

    }
}

所以一定要鎖變化的量,如果只是訪問沒變化便不需上鎖。

JUC簡單應用

什麼是JUC,就是java的一個併發包,全名叫:java.util.concurrent

package com.linyh.syn;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @author lyh
 * @date 2020/10/31 16:34
 * 說明:
 */
public class JUCTest {


    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        for (int i = 0; i < 10000; i++) {

            // 建立一個執行緒,將該執行緒名字存入list
            new Thread(()->list.add(Thread.currentThread().getName())).start();

        }

        // 等3s,因為有的執行緒可能還被阻塞,還沒新增
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

死鎖

  • 執行緒A擁有a資源,需等待b資源才可以繼續執行,執行緒B擁有b資源,需要等待a資源才可以繼續進行,這就造成了死鎖
  • 一個同步塊內不能擁有2個鎖,否則會造成死鎖,示例如下:
package com.linyh.syn;

/**
 * @author lyh
 * @date 2020/10/31 17:00
 * 說明:
 */


class Mirror {

}

class Lipstick {

}

public class DeadLock extends Thread{


    private static Mirror mirror = new Mirror();
    private static Lipstick lipstick = new Lipstick();
    private String name;
    private int choose;

    public DeadLock(String name, int choose) {
        this.name = name;
        this.choose = choose;
    }

    @Override
    public void run() {

        makeUp();
    }

    public void makeUp() {
        if (choose == 0) {
            synchronized (mirror) {
                System.out.println("獲得了鏡子");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lipstick) {
                    System.out.println("獲得了口紅");
                }
            }
        }else {
            synchronized (lipstick) {
                System.out.println("獲得了口紅");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (mirror) {
                    System.out.println("獲得了鏡子");
                }
            }
        }
    }

    public static void main(String[] args) {

        new DeadLock("小紅", 0).start();
        new DeadLock("小櫻", 1).start();
    }
}

這就造成了死鎖:

Lock(可重入鎖ReentrantLock)

之前買票的例子:

package com.linyh.syn;

/**
 * @author lyh
 * @date 2020/10/31 21:28
 * 說明:
 */
public class LockTest implements Runnable{

    private int tickets = 10;
    @Override
    public void run() {

        while (true) {
            if (tickets <= 0) {
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "買了票" + tickets--);
        }
    }


    public static void main(String[] args) {

        LockTest test = new LockTest();

        new Thread(test, "A").start();
        new Thread(test, "B").start();
    }
}

可以看到,tickets = 0時已經無法買票,但是A還是買了0,這裡出了問題,下面是手動加鎖:

package com.linyh.syn;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author lyh
 * @date 2020/10/31 21:28
 * 說明:
 */
public class LockTest implements Runnable{

    private int tickets = 10;
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {

        while (true) {
            lock.lock();
            try {
                if (tickets <= 0) {
                    return;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "買了票" + tickets--);
            } finally {
                lock.unlock();
            }

        }
    }


    public static void main(String[] args) {

        LockTest test = new LockTest();

        new Thread(test, "A").start();
        new Thread(test, "B").start();
    }
}

synchronized和ReentrantLock的區別

  • synchronized可以以作用在程式碼塊和方法,reentrantlock只能作用在程式碼塊
  • synchronized進入作用域會自動上鎖,出了作用域會自動解鎖(隱式鎖),reentrantlock只能手動上鎖(顯式鎖)
  • reentrantlock排程花費的時間較少
  • 使用順序:lock>synchronized程式碼塊>synchronized方法

生產者消費者問題

  1. 需要滿足執行緒同步
  2. 還要滿足執行緒通訊

管程法(即生產者、消費者、緩衝區)

package com.linyh.syn;

/**
 * @author lyh
 * @date 2020/11/1 7:56
 * 說明:
 */
public class PCTest {

    public static void main(String[] args) {

        Container container = new Container();
        new Producer(container).start();
        new Consumer(container).start();
    }
}

// 生產者
class Producer extends Thread {

    private Container container;

    public Producer(Container container) {
        this.container = container;
    }

    @Override
    public void run() {

        for (int i = 0; i < 1000; i++) {
            Product product = new Product(i);
            container.push(product);
            System.out.println("生產了" + product.id);
        }
    }
}

// 消費者
class Consumer extends Thread {

    private Container container;

    public Consumer(Container container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            Product product = container.pop();
            System.out.println("消耗了產品" + product.id);
        }
    }
}

// 產品
class Product {
    public int id;

    public Product(int id) {
        this.id = id;
    }
}

// 緩衝區
class Container {

    private Product[] products = new Product[10];
    private int size = 10;
    private int count = 0;

    public synchronized void push(Product product) {

        // 如果緩衝區已滿
        if (count >= size) {
            // 等待消費者放入產品
            try {
                // 先阻塞在者,至於什麼時候甦醒是由生產者決定
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        }
        products[count++] = product;

        this.notifyAll();


    }

    public synchronized Product pop() {
        // 如果緩衝區中沒有東西
        if (count <= 0) {
            // 等待生產者放入產品
            try {
                // 先阻塞在者,至於什麼時候甦醒是由生產者決定
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        Product p = products[count];


        // 喚醒生產者
        this.notifyAll();
        return p;
    }

}

訊號燈法

package com.linyh.syn;

/**
 * @author lyh
 * @date 2020/11/1 9:36
 * 說明:
 */
public class TVTest {

    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}


class Player extends Thread {

    private TV tv;

    public Player(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {

        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                tv.play("舞蹈風暴2");
            }else {
                tv.play("秦時明月之滄海橫流");
            }
        }
    }
}

class Watcher extends Thread {

    private TV tv;

    public Watcher(TV tv) {
        this.tv = tv;
    }

    @Override
    public void run() {

        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}

class TV {

    // 表演/觀看的節目
    private String program;
    /*
    演員表演時間 true
    觀眾觀看時間 false
     */
    private boolean flag = true;

    public synchronized void play(String program) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.program = program;
        System.out.println("表演了--->" + this.program);
        flag = false;
        this.notifyAll();
    }

    public synchronized void watch() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("觀看了--->" + this.program);
        flag = true;
        this.notifyAll();
    }
}

執行緒池

  • 如果每有執行緒池,每次需要執行緒都需要建立,非常消耗資源
  • 執行緒池中的執行緒用時直接取,用完後再放入,可實現重複利用
  • 便於管理執行緒池中的執行緒
    • corePoolSize:核心池大小
    • maximumPoolSize:最大執行緒數
    • keepAliveTime:執行緒每有任務時最多保持多長時間銷燬
package com.linyh.syn;


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

/**
 * @author lyh
 * @date 2020/11/1 10:06
 * 說明:
 */
public class TestPool {

    public static void main(String[] args) {

        ExecutorService service = Executors.newFixedThreadPool(10);

        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        service.shutdown();
    }
}

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

因為Callable需要呼叫,所以需要先submit再get(呼叫),但是Runable直接提交或執行就可以了。