爲了在簡歷上寫掌握Java多線程和併發編程,做了兩萬字總結!!!

概述

面試中,多線程和併發編程已經是必不可少的了,我經常看到此類問題,當時也簡單瞭解過,什麼繼承Thread類,實現Runnable接口,這些都被說爛了,知道這些當然是遠遠不夠的,於是這幾天搜索相關資料惡補了一下,爲了方便後期複習,在此做個總結。

繼承Thread類

這個可以說是很原始的方式,就是繼承Thread類,重寫run方法。

package com.hzy;

public class Main {
    public static void main(String[] args) {
        new MyThread().start();
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

由於Java是單繼承的,所以這種方法用的比較少,一般都是用Runnable接口

實現Runnable接口

這裏給出幾個常用的構造方法
在這裏插入圖片描述
先給出一個傳統的方法實現

package com.hzy;

public class Main {
    public static void main(String[] args) {
        new Thread(new MyThread()).start();
        new Thread(new MyThread(),"賀志營").start();
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

我們也可以通過匿名內部類實現

package com.hzy;

public class Main {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }).start();
    }
}

還可以通過Lamata表達式實現

package com.hzy;

public class Main {
    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }).start();
    }
}

實現Callable接口

Callable接口可以接收返回值,可以拋出異常,重寫的是call方法

package com.hzy;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 創建執行服務
        ExecutorService service = Executors.newFixedThreadPool(3);
        // 提交執行
        Future<Boolean> future1 = service.submit(new MyThread());
        Future<Boolean> future2 = service.submit(new MyThread());
        Future<Boolean> future3 = service.submit(new MyThread());
        // 獲取返回值
        Boolean b1 = future1.get();
        Boolean b2 = future2.get();
        Boolean b3 = future3.get();

        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b3);
        // 關閉服務
        service.shutdown();
    }
}

class MyThread implements Callable<Boolean> {

    @Override
    public Boolean call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        return true;
    }
}

另外還可以通過FutureTask適配器創建
在這裏插入圖片描述
在這裏插入圖片描述

package com.hzy;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 創建一個適配器
        FutureTask futureTask = new FutureTask(new MyThread());
        new Thread(futureTask,"A").start();
        Boolean o = (Boolean) futureTask.get();
        System.out.println(o);
    }
}

class MyThread implements Callable<Boolean> {

    @Override
    public Boolean call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        return true;
    }
}

線程的五大狀態

在這裏插入圖片描述
創建狀態
所謂的創建狀態,也就是我們的new Thread();
就緒狀態
所謂的就緒狀態,就是我們myThread.start();
運行狀態
運行狀態就是我們的代碼執行。
阻塞狀態
當線程等待(wait)、同步(synchronized)、sleep和join的時候
死亡狀態
run()結束、main()結束
線程常用方法

  • setPriority(int new Priority)更改線程的優先級
  • sleep(long millis)讓當前線程進入休眠
  • join()相當於插隊,插入的線程執行結束後,被插隊線程繼續執行。
  • yield()線程禮讓,暫停當前的線程,並把該線程狀態轉化爲就緒狀態
  • interrupt()中斷線程(不建議用)
  • isAlive()判斷線程是否處於存活狀態

多線程買票案例

當多個線程操作同一個資源的時候,就會出現線程安全問題。假設我們有100張票,在三個窗口同時賣,也就是我們有三個線程去買票。

package com.hzy;

public class Main {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        new Thread(myThread,"窗口1").start();
        new Thread(myThread,"窗口2").start();
        new Thread(myThread,"窗口3").start();
        new Thread(myThread,"窗口4").start();
        new Thread(myThread,"窗口5").start();
    }
}

class MyThread implements Runnable {
    private int ticket = 100;
    private boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    public void buy() {
        if (ticket <= 0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(100);// 模擬買票延遲100ms
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":"+ ticket--);
    }
}

在這裏插入圖片描述
可以看出,出現了線程安全問題,原因是在對ticket變爲0之前,有多個線程同時進來。
解決辦法,可以通過synchronized線程同步,可以使用同步方法和同步代碼塊進行解決。同步方法,也就是在方法上加一個synchronized關鍵字。

package com.hzy;

public class Main {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        new Thread(myThread,"窗口1").start();
        new Thread(myThread,"窗口2").start();
        new Thread(myThread,"窗口3").start();
    }
}

class MyThread implements Runnable {
    private int ticket = 100;
    private boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    public synchronized void buy() {
        if (ticket <= 0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(100);// 模擬買票延遲100ms
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":"+ ticket--);
    }
}

同步代碼塊鎖的是一個對象,即是需要變化的量

package com.hzy;

public class Main {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        new Thread(myThread,"窗口1").start();
        new Thread(myThread,"窗口2").start();
        new Thread(myThread,"窗口3").start();
    }
}

class MyTicket {
    int ticket;

    public MyTicket(int ticket) {
        this.ticket = ticket;
    }
}

class MyThread implements Runnable {
    MyTicket myTicket = new MyTicket(100);
    private boolean flag = true;
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    public void buy() {
        synchronized (myTicket) {
            if (myTicket.ticket <= 0) {
                flag = false;
                return;
            }
            try {
                Thread.sleep(100);// 模擬買票延遲100ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":"+ myTicket.ticket--);
        }

    }
}

死鎖

所謂死鎖,也就是A擁有A資源的同時想要B的資源,B擁有B資源的同時想要A的資源。

package com.hzy;

public class Main {
    public static void main(String[] args){
        A a = new A();
        B b = new B();
        new Thread(new MyThread(a,b,0)).start();
        new Thread(new MyThread(a,b,1)).start();
    }
}
class A {

}
class B {

}

class MyThread implements Runnable {
    private A a;
    private B b;
    private int choice;
    public MyThread(A a,B b,int choice) {
        this.a = a;
        this.b = b;
        this.choice = choice;
    }
    @Override
    public void run() {
        if (choice == 0) {
            synchronized (a) {
                System.out.println(Thread.currentThread().getName() + "獲得" + a);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println(Thread.currentThread().getName() + "獲得" + b);
                }
            }
        }else {
            synchronized (b) {
                System.out.println(Thread.currentThread().getName() + "獲得" + b);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (a) {
                    System.out.println(Thread.currentThread().getName() + "獲得" + a);
                }
            }
        }
    }
}

可以看出,A需要B資源,B需要A資源,出現了死鎖的狀態。
在這裏插入圖片描述

Lock鎖

Lock是一個接口,而不是一個關鍵字,他是一個顯示鎖,只能鎖同步代碼塊,不能鎖方法,可以顯示加鎖,釋放鎖,可以指定喚醒某一個線程,Lock鎖有一個實現類是ReentrantLock,可重入鎖(遞歸鎖),在這裏說一下,所有的鎖都是可重入鎖,也就是如果我們獲得了外面的鎖之後,會自動獲取裏面的鎖。
在這裏插入圖片描述
正常的,如果我們不加鎖,會出現線程安全問題

package com.hzy;

public class Main {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        new Thread(myThread).start();
        new Thread(myThread).start();
        new Thread(myThread).start();
    }
}
class MyThread implements Runnable {

    private int ticket = 100;
    @Override
    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(ticket --);
        }
    }
}

在這裏插入圖片描述
可以通過RenntrantLock進行加鎖,顯示鎖,記得解鎖。

package com.hzy;

import java.util.concurrent.locks.ReentrantLock;

public class Main {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        new Thread(myThread).start();
        new Thread(myThread).start();
        new Thread(myThread).start();
    }
}
class MyThread implements Runnable {

    private int ticket = 100;
    // 定義lock鎖
    ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            lock.lock();
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(ticket --);
            }else {
                break;
            }
            lock.unlock();
        }
    }
}

比ReentrantLock更細的鎖是,ReentrantReadWriteLock,這裏有一個讀鎖,一個寫鎖,
在這裏插入圖片描述

package com.hzy;

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
        for (int i = 0; i < 5; i++) {
            int temp = i;
            new Thread(()->{
                myReadWriteLock.put(temp + "",temp + "");
            },temp + "").start();
        }

        for (int i = 0; i < 5; i++) {
            int temp = i;
            new Thread(()->{
                myReadWriteLock.get(temp + "");
            },temp + "").start();
        }
    }
}

class MyReadWriteLock {
    private volatile Map<String,String> map = new HashMap<>();

    public void put(String key,String value) {
        System.out.println(Thread.currentThread().getName() + "寫入" + key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName() + "寫入完成");
    }

    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "讀取" + key);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "讀取完成");
    }
}

如果不加鎖的話,在寫入完成之前會被其他線程插入
在這裏插入圖片描述
而我們想要的是,在寫入的時候,只能是一個線程,而讀取的時候,可以是多個線程。

package com.hzy;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Main {
    public static void main(String[] args) {
        MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
        for (int i = 0; i < 5; i++) {
            int temp = i;
            new Thread(()->{
                myReadWriteLock.put(temp + "",temp + "");
            },temp + "").start();
        }

        for (int i = 0; i < 5; i++) {
            int temp = i;
            new Thread(()->{
                myReadWriteLock.get(temp + "");
            },temp + "").start();
        }
    }
}

class MyReadWriteLock {
    private volatile Map<String,String> map = new HashMap<>();
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public void put(String key,String value) {
        readWriteLock.writeLock().lock();
        System.out.println(Thread.currentThread().getName() + "寫入" + key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName() + "寫入完成");
        readWriteLock.writeLock().unlock();
    }

    public void get(String key) {
        readWriteLock.readLock().lock();
        System.out.println(Thread.currentThread().getName() + "讀取" + key);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "讀取完成");
        readWriteLock.readLock().unlock();
    }
}

可以看出,在寫入的時候,是一個寫入,一個寫入完成,在讀取的時候,可能會有多個讀取。
在這裏插入圖片描述
synchronized和Lock的區別

  • synchronized是關鍵字,Lock是類
  • synchronized無法獲取鎖的狀態,Lock可以
  • synchronized會自動釋放鎖,Lock需要手動
  • synchronized沒有Lock鎖靈活(Lock鎖可以自己定製)

生產者消費者問題

這是一個經典的線程通信問題,也就是不同線程之間有聯繫,生產者生產的東西放到緩衝區,如果緩衝區滿了,生產者進入堵塞,緩衝區空了,消費者堵塞。
常用的方法有wait(),notify(),notifyAll()

package com.hzy;

import java.util.concurrent.locks.ReentrantLock;

public class Main {
    public static void main(String[] args){
        Buffer buffer = new Buffer();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("生產者生產了" + i);
                buffer.put();
            }
        },"生產者").start();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("消費者消費了" + i);
                buffer.get();
            }
        },"消費者").start();
    }
}

// 緩衝區
class Buffer {
    private int len = 0;
    public synchronized void put() {
        if (len < 10) {
            len ++;
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        notifyAll();
    }
    public synchronized void get() {
        if (len > 0) {
            len --;
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        notifyAll();
    }
}

學習了Lock鎖,這裏的生產者消費者問題,可以通過Lock鎖實現,可以指定喚醒某個線程,常用的方法是await,signal。
這裏給出緩衝區

// 緩衝區
class Buffer {
    private int len = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();// 該對象可以設置一些條件
    public void put() {
        lock.lock();
        if (len < 10) {
            len ++;
        } else {
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        condition.signalAll();
        lock.unlock();
    }
    public void get() {
        lock.lock();
        if (len > 0) {
            len --;
        } else {
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        condition.signalAll();
        lock.unlock();
    }
}

其中這裏引入了Condition,他可以指定喚醒某個線程,這裏我們演示三個線程ABC。

package com.hzy;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {
    public static void main(String[] args){
        Buffer buffer = new Buffer();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                buffer.a();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                buffer.b();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                buffer.c();
            }
        },"C").start();
    }
}

// 緩衝區
class Buffer {
    private int flag = 1;// 1執行A,2執行B,3執行C
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();// 該對象可以設置一些條件
    Condition condition2 = lock.newCondition();// 該對象可以設置一些條件
    Condition condition3 = lock.newCondition();// 該對象可以設置一些條件
    public void a() {
        lock.lock();
        try {
            while (flag != 1) {
                condition1.await();
            }
            System.out.println("A");
            flag = 2;
            condition2.signal();// 指定喚醒B
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void b() {
        lock.lock();
        try {
            while (flag != 2) {
                condition2.await();
            }
            System.out.println("B");
            flag = 3;
            condition3.signal();// 指定喚醒C
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void c() {
        lock.lock();
        try {
            while (flag != 3) {
                condition3.await();
            }
            System.out.println("C");
            flag = 1;
            condition1.signal();// 指定喚醒A
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

結果都是ABC、ABC…
在這裏插入圖片描述

八鎖問題

package com.hzy;
import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        }, "B").start();
    }
}

class Phone {
    public synchronized void sendMsg() {
        System.out.println("sendMsg");
    }

    public synchronized void call() {
        System.out.println("call");
    }
}

先輸出sendMsg,不要理解爲是先調用了sendMsg,而是因爲A線程先獲得了鎖。
在這裏插入圖片描述

package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        }, "B").start();
    }
}

class Phone {
    public synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

    public synchronized void call() {
        System.out.println("call");
    }
}

這個例子,再次解釋了是因爲sendMsg先獲得的鎖,這裏的synchronized鎖的對象是調用者,這兩個方法用的是同一把鎖,誰先拿到,誰先執行
在這裏插入圖片描述

package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        }, "B").start();
    }
}

class Phone {
    public synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

//    public synchronized void call() {
//        System.out.println("call");
//    }
        public void call() {
        System.out.println("call");
    }
}

這裏把call方法的synchronized去掉了,會先輸出哪個呢,答案是先輸出call,因爲他不去獲取鎖資源,所以直接輸出了
在這裏插入圖片描述

package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            phone1.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        }, "B").start();
    }
}

class Phone {
    public synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

    public synchronized void call() {
        System.out.println("call");
    }
}

這裏用兩個phone對象,分別調用sendMsg和call方法,會先執行哪個,答案是先執行call,因爲這裏的鎖鎖的是對象,而他們不是同一個對象,所以資源不受影響。
在這裏插入圖片描述

package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        }, "B").start();
    }
}

class Phone {
    public static synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

    public static synchronized void call() {
        System.out.println("call");
    }
}

這裏是通過一個對象,去調用靜態的同步方法,看看是先sendMsg還是call,答案是sentMsg,因爲這裏鎖的是Class對象(只有一個)
在這裏插入圖片描述

package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            phone1.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        }, "B").start();
    }
}

class Phone {
    public static synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

    public static synchronized void call() {
        System.out.println("call");
    }
}

這裏是通過兩個對象去調用sentMsg和call,結果是什麼呢,答案還是先執行sentMsg,因爲鎖的是Class對象,所以不管是phone1還是phone2,都是屬於Phone的Class對象。
在這裏插入圖片描述


package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        }, "B").start();
    }
}

class Phone {
    public static synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

    public synchronized void call() {
        System.out.println("call");
    }
}

這裏鎖的一個是靜態同步方法,一個是普通同步方法,用一個對象對調用,結果是什麼呢,答案是先call,因爲靜態同步方法鎖的是Class對象,而普通同步方法鎖的是調用者,鎖的不是同一個東西。
在這裏插入圖片描述

package com.hzy;


import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args){
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(()->{
            phone1.sendMsg();
        }, "A").start();
        try {
            TimeUnit.SECONDS.sleep(1);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        }, "B").start();
    }
}

class Phone {
    public static synchronized void sendMsg() {
        try {
            TimeUnit.SECONDS.sleep(3);// 休息一秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

    public synchronized void call() {
        System.out.println("call");
    }
}

這裏用兩個對象去調用靜態同步方法sendMsg和普通同步方法call,會先輸出哪個,答案是call,因爲sendMsg鎖的是Class,而call鎖的是調用者,不是同一個一個東西
在這裏插入圖片描述

線程池

線程的創建跟Callable差不多,也是用ExecutorService

package com.hzy;

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

public class Main {
    public static void main(String[] args){

        ExecutorService service = Executors.newFixedThreadPool(10);// 創建固定個數線程
        // ExecutorService service2 = Executors.newCachedThreadPool();// 創建動態線程(自適應大小)
        // ExecutorService service1 = Executors.newSingleThreadExecutor();// 創建單個線程
        // 執行
        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());
    }
}

可以看出,在線程池中拿了三個線程
在這裏插入圖片描述
在阿里巴巴開發手冊中有規定,創建線程池要用ThreadPoolExecutor
在這裏插入圖片描述
對於ThreadPoolExecutor的學習,就從七大參數四種拒絕策略
在這裏插入圖片描述

int corePoolSize// 核心線程池大小
int maximumPoolSize// 最大核心線程池大小
long keepAliveTime// 超時存活時間
TimeUnit unit//  超時單位
BlockingQueue<Runnable> workQueue// 阻塞隊列
ThreadFactory threadFactory// 線程工廠,用於創建線程
RejectedExecutionHandler handler// 拒絕策略

在這裏插入圖片描述

AbortPolicy());// 銀行滿了還有人進來,不處理,拋出異常(默認)
CallerRunsPolicy();// 銀行滿了,不處理,哪裏來的去哪裏,一般拋給main線程
DiscardPolicy();// 銀行滿了,把該線程丟掉,不拋異常
DiscardOldestPolicy();// 銀行滿了,會和先來的線程競爭,不拋異常
package com.hzy;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args){

        /**
         * 用一個銀行的例子進行講解這七大參數
         */
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,// 兩個常開營業窗口
                5,// 五個窗口,其中三個應急用
                3,// 超時存貨時間
                TimeUnit.SECONDS,// 超時時間單位
                new LinkedBlockingDeque<>(3),// 銀行候客區大小
                Executors.defaultThreadFactory(),// 默認線程池工廠
                new ThreadPoolExecutor.AbortPolicy());// 銀行滿了還有人進來,不處理,拋出異常(默認)
                // new ThreadPoolExecutor.CallerRunsPolicy();// 銀行滿了,不處理,哪裏來的去哪裏,一般拋給main線程
                // new ThreadPoolExecutor.DiscardPolicy();// 銀行滿了,把該線程丟掉,不拋異常
                // new ThreadPoolExecutor.DiscardOldestPolicy();// 銀行滿了,會和先來的線程競爭,不拋異常
        for (int i = 0; i < 8; i++) {
            threadPool.execute(()->{
                System.out.println(Thread.currentThread().getName());
            });
        }
        // 關閉連接
        threadPool.shutdown();
    }
}

在定義線程池最大大小的時候,一般有兩種策略CPU密集型IO密集型,所謂CPU密集型,也就是,幾核的CPU就定義爲幾,我的是八核,所以定義爲8,Runtime.getRuntime().availableProcessors();// 獲取CPU的核數,IO密集型,就是判斷程序中有多少個非常耗IO線程的程序,最大線程池的大小要大於這個值即可。

volatile

說起volatile,不難想出三大特性保證可見性不保證原子性禁止指令重排
在講解volatile之前,需要講解一個東西,Java內存模型(Java Memory Model,JMM),當線程讀取內存中的一個變量時,會先把這個變量拷貝到CPU的高速緩存區,然後對其進行操作,操作完成後,會把該變量寫入到內存中。在單線程中是不會出現任何問題的,但是在多線程中就會有問題,當線程1讀取了該變量a=1到緩存區進行了加1操作,還沒寫到內存中,線程2讀取了內存中的變量a=1也進行加1操作,然後線程1寫入內存a=2,線程2也寫入a=2到內存,那麼最後,該變量的值是2,而不是3(出現了線程安全問題)。我們想要當線程1進行了加1操作之後,讓線程2知道,這就是volatile的作用了,可以保證可見性,也就是,當線程1對a變量進行了加1操作,會直接寫入到內存中(立即馬上),並且通知線程2,變量被修改了,要求線程2緩衝區的值去內存中重新讀取。但是,加1操作不是原子性的(三步,首先讀取a變量的值,然後對其進行加1操作,然後賦值給a),也就是說,當線程1讀取a變量到緩衝區後,還沒有修改a的值,此時線程2進來了,讀取了a的值,並且對其進行了加1操作,由於可見性,會把線程1緩衝區的值進行修改,但是,線程1中的CPU已經讀取了緩衝區的值,而且是更新前的值,所以出現了線程安全問題,也是volatile不保證原子性的問題。於是就需要加1操作是原子性操作,於是就有了一個automic包,通過該包下的方法,可以實現加1的原子性操作(還有其他原子性操作)。
在這裏插入圖片描述
在原子操作裏的自增,其實用的是自旋鎖,也就是一直進行比較並交換。
在這裏插入圖片描述
說起原子性操作,其實得聊聊CAS(Compare And Swap,比較並交換),如果我們想要修改某個值num,那麼我們可以通過一個方法compareAndSet(5,6)意思是,我們期望num的值是5,如果是,就修改爲6。但是這就會有一個問題,我們期望的num是5,如果有其他線程把5修改爲了8,然後又修改爲了5,最終是5,但是已經被修改過一次了,這就是ABA問題。我們可以通過AtomicStampedReference,也就是原子引用,在創建的時候,有一個印記,相當於版本號,每被修改一次,版本號都被更新,所以,當出現ABA問題的時候,我們就可以清楚的知道,被修改了多少次。
在這裏插入圖片描述

還有一個特性就是禁止指令重排,既然要禁止他,那麼就得知道什麼是指令重排,所謂指令重排,也就是,爲了提高程序執行效率,寄存器對代碼的優化,如果我們有一段代碼如下

a = 1;// 語句1
b = 2;// 語句2
a = a + 1;// 語句3
b = a + 1;// 語句4

所謂指令重排,也就是在不影響單線程程序程序結果的情況下進行最優執行排序,可以看出,語句1和語句2的執行順序並不會影響程序的結果,最終a的值爲2,b的值爲3。下面看這個代碼Instance instance = new Instance();
從微觀的角度看,這條語句可以分解爲三步,一是分配內存空間,二是初始化對象三是指向該內存空間,其中二和三會發生指令重排,如果在多線程的情況下,線程A分配了內存空間,並且執行了該內存空間(沒有初始化對象),線程B進行了訪問該內存空間,這就會出錯了。而volatile就是禁止指令重排的,也並非絕對的禁止,假設我們有1、2、3、4、5條語句

a = 1;
b = 2;
c = 3;
d = 4;
e = 5;

如果我們對c操作進行了volatile修飾,那麼a和b依然可以指令重排,d和e也可以指令重排,ab和de不能指令重排了,c把他們分開了(專業術語是給c加了個內存屏障)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章