Review-Java多線程

對volatile的理解

volatile是Java虛擬機提供的輕量級的同步機制

JMM你談談

JMM(Java內存模型Java Memory Model,簡稱JMM)本身是一種抽象的概念 並不真實存在,它描述的是一組規則或規範通過規範定製了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式.
JMM關於同步規定:
1.線程解鎖前,必須把共享變量的值刷新回主內存
2.線程加鎖前,必須讀取主內存的最新值到自己的工作內存
3.加鎖解鎖是同一把鎖

由於JVM運行程序的實體是線程,而每個線程創建時JVM都會爲其創建一個工作內存(有些地方成爲棧空間),工作內存是每個線程的私有數據區域,而Java內存模型中規定所有變量都存儲在主內存,主內存是共享內存區域,所有線程都可訪問,但線程對變量的操作(讀取賦值等)必須在工作內存中進行,首先要將變量從主內存拷貝到自己的工作空間,然後對變量進行操作,操作完成再將變量寫回主內存,不能直接操作主內存中的變量,各個線程中的工作內存儲存着主內存中的變量副本拷貝,因此不同的線程無法訪問對方的工作內存,此案成間的通訊(傳值) 必須通過主內存來完成,其簡要訪問過程如下圖
在這裏插入圖片描述

可見性

通過前面對JMM的介紹,我們知道
各個線程對主內存中共享變量的操作都是各個線程各自拷貝到自己的工作內存操作後再寫回主內存中的.
這就可能存在一個線程AAA修改了共享變量X的值還未寫回主內存中時 ,另外一個線程BBB又對內存中的一個共享變量X進行操作,但此時A線程工作內存中的共享比那裏X對線程B來說並不不可見.這種工作內存與主內存同步延遲現象就造成了可見性問題.

每個線程都有獨佔的內存區域,如操作數棧,本地變量表等。線程本地內存保存了引用變量在堆內存中的副本,線程對變量的所有操作都在本地內存區域中進行,執行結束後再同步到堆內存中。這裏必然有一個時間差,在這個時間差內,該線程對副本的操作

可見性代碼演示



import java.util.concurrent.TimeUnit;

class MyData {

    //volatile    int number = 0;
    int number = 0;

    public void addTo60() {
        this.number = 60;
    }
}


/**
 * 線程內存可見性  主內存 和 線程工作內存
 *
 * @author niugang
 */
public class VolatileDemo {


    public static void main(String[] args) {
        final MyData myData = new MyData();
        new Thread() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "\t come in");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();

                }
                myData.addTo60();
                System.out.println(Thread.currentThread().getName() + "\t update number value:" + myData.number);
            }
        };

        while (myData.number == 0) {
            //main線程一直等待循環,直到number值不再爲0
        }
        System.out.println(Thread.currentThread().getName() + "\t mission is over");
    }


}

原子性

number++在多線程下是非線程安全的,如何不加synchronized解決? JUC包中AtomicInteger
在這裏插入圖片描述

有序性

計算機在執行程序時,爲了提高性能,編譯器和處理器常常會做指令重排,一把分爲以下3中
在這裏插入圖片描述
單線程環境裏面確保程序最終執行結果和代碼順序執行的結果一致.
處理器在進行重新排序是必須要考慮指令之間的數據依賴性

多線程環境中線程交替執行,由於編譯器優化重排的存在,兩個線程使用的變量能否保持一致性是無法確定的,結果無法預測

public void mySort(){
    int x=11;//語句1
    int y=12;//語句2
    x=x+5;//語句3
    y=x*x;//語句4
}
/**1234
2134
1324
問題:
請問語句4 可以重排後變成第一條碼?
存在數據的依賴性 沒辦法排到第一個
*/

int a ,b ,x,y=0;
線程1 線程2
x=a; y=b;
b=1; a=2;

x=0 y=0
如果編譯器對這段代碼進行執行重排優化後,可能出現下列情況:
線程1 線程2
b=1; a=2;
x=a; y=b;

x=2 y=1
這也就說明在多線程環境下,由於編譯器優化重排的存在,兩個線程使用的變量能否保持一致是無法確定的.

禁止指令重排小總結(瞭解)

在這裏插入圖片描述
在這裏插入圖片描述
以上 可見性 原子性 有序性 保證 保證線程安全性

哪些地方用到過volatile

單例模式DCL(雙重檢查加鎖)代碼

public class SingletonDemo {

    private static volatile SingletonDemo instance=null;
    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t 構造方法");
    }

    /**
     * 雙重檢測機制
     * @return
     */
    public static SingletonDemo getInstance(){
        if(instance==null){
            synchronized (SingletonDemo.class){
                if(instance==null){
                    instance=new SingletonDemo();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 1; i <=10; i++) {
            new Thread(() ->{
                SingletonDemo.getInstance();
            },String.valueOf(i)).start();
        }
    }
}

DCL(雙端檢鎖) 機制不一定線程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排
原因在於某一個線程在執行到第一次檢測,讀取到的instance不爲null時,instance的引用對象可能沒有完成初始化.
instance=new SingletonDem(); 可以分爲以下步驟(僞代碼)

memory=allocate();//1.分配對象內存空間
instance(memory);//2.初始化對象
instance=memory;//3.設置instance的指向剛分配的內存地址,此時instance!=null

步驟2和步驟3不存在數據依賴關係.而且無論重排前還是重排後程序執行的結果在單線程中並沒有改變,因此這種重排優化是允許的.
memory=allocate();//1.分配對象內存空間
instance=memory;//3.設置instance的指向剛分配的內存地址,此時instance!=null 但對象還沒有初始化完.
instance(memory);//2.初始化對象
但是指令重排只會保證串行語義的執行一致性(單線程) 並不會關心多線程間的語義一致性
所以當一條線程訪問instance不爲null時,由於instance實例未必完成初始化,也就造成了線程安全問題.

CAS

比較並交換


public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t current"+atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5, 2014)+"\t current"+atomicInteger.get());
    }
}
 

CAS底層原理?對UnSafe的理解

atomicInteger.getAndIncrement();方法的源代碼:


/**
 * Atomically increments by one the current value.
 *
 * @return the previous value
 */
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

UnSafe

在這裏插入圖片描述
UnSafe
是CAS的核心類 由於Java 方法無法直接訪問底層 ,需要通過本地(native)方法來訪問,UnSafe相當於一個後面,基於該類可以直接操作特額定的內存數據.UnSafe類在於sun.misc包中,其內部方法操作可以向C的指針一樣直接操作內存,因爲Java中CAS操作的助興依賴於UNSafe類的方法.

注意:UnSafe類中所有的方法都是native修飾的,也就是說UnSafe類中的方法都是直接調用操作底層資源執行響應的任務

變量ValueOffset,便是該變量在內存中的偏移地址,因爲UnSafe就是根據內存偏移地址獲取數據的
在這裏插入圖片描述
變量value和volatile修飾,保證了多線程之間的可見性.

CAS是什麼

在這裏插入圖片描述
var1 AtomicInteger對象本身.
var2 該對象值的引用地址
var4 需要變動的數值
var5 是用過var1 var2找出內存中紳士的值
用該對象當前的值與var5比較
如果相同,更新var5的值並且返回true
如果不同,繼續取值然後比較,直到更新完成

假設線程A和線程B兩個線程同時執行getAndAddInt操作(分別在不同的CPU上):

1.AtomicInteger裏面的value原始值爲3,即主內存中AtomicInteger的value爲3,根據JMM模型,線程A和線程B各自持有一份值爲3的value的副本分別到各自的工作內存.

2.線程A通過getIntVolatile(var1,var2) 拿到value值3,這是線程A被掛起.

3.線程B也通過getIntVolatile(var1,var2) 拿到value值3,此時剛好線程B沒有被掛起並執行compareAndSwapInt方法比較內存中的值也是3 成功修改內存的值爲4 線程B打完收工 一切OK.

4.這是線程A恢復,執行compareAndSwapInt方法比較,發現自己手裏的數值和內存中的數字4不一致,說明該值已經被其他線程搶先一步修改了,那A線程修改失敗,只能重新來一遍了.

5.線程A重新獲取value值,因爲變量value是volatile修飾,所以其他線程對他的修改,線程A總是能夠看到,線程A繼續執行compareAndSwapInt方法進行比較替換,直到成功.

CAS缺點

循環時間長開銷很大

在這裏插入圖片描述

只能保證一個共享變量的原子性

在這裏插入圖片描述

ABA問題

原子類AtomicInteger的ABA問題談談?原子更新引用

ABA問題的產生

在這裏插入圖片描述

原子引用

class User{
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class AtomicReferenceDemo {
    public static void main(String[] args) {
        User zs = new User("zs", 22);
        User ls = new User("ls", 22);
        AtomicReference<User> userAtomicReference = new AtomicReference<>();
        userAtomicReference.set(zs);
        System.out.println(userAtomicReference.compareAndSet(zs,ls)+"\t"+userAtomicReference.get().toString());
 System.out.println(userAtomicReference.compareAndSet(zs,ls)+"\t"+userAtomicReference.get().toString());
    }
}

執行結果:

true User{name=‘ls’, age=22}
false User{name=‘ls’, age=22}

時間戳原子引用

AtomicStampedReference

ArrayList 線程安全解決方案

限制不可以使用vector和Collections工具類解決

List線程CopyOnWriteArrayList

   /** 寫時複製 copyOnWrite 容器即寫時複製的容器 往容器添加元素的時候,不直接往當前容器object[]添加,而是先將當前容器object[]進行

copy 複製出一個新的object[] newElements 然後向新容器object[] newElements 裏面添加元素 添加元素後,
 再將原容器的引用指向新的容器 setArray(newElements);

這樣的好處是可以對copyOnWrite容器進行併發的讀,而不需要加鎖 因爲當前容器不會添加任何容器.所以copyOnwrite容器也是一種
 讀寫分離的思想,讀和寫不同的容器.*/
public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

set線程CopyOnwriteHashSet

map線程ConcurrentHashMap

公平鎖/非公平鎖/可重入鎖/遞歸鎖/自旋鎖

公平鎖和非公平鎖

公平鎖
是指多個線程按照申請鎖的順序來獲取鎖類似排隊打飯 先來後到
非公平鎖
是指在多線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取到鎖,在高併發的情況下,有可能造成優先級反轉或者飢餓現象

公平鎖/非公平鎖
併發包ReentrantLock的創建可以指定構造函數的boolean類型來得到公平鎖或者非公平鎖 默認是非公平鎖
在這裏插入圖片描述
通過構造函數指定該鎖是否是公平鎖 默認是非公平鎖 非公平鎖的優點在於吞吐量必公平鎖大.

對於synchronized而言 也是一種非公平鎖.

可重入鎖(又名遞歸鎖)

在這裏插入圖片描述
ReentrantLock/synchronized就是一個典型的可重入鎖

可重入鎖最大的作用就是避免死鎖

class Phone{
    public synchronized void sendSms() throws Exception{
        System.out.println(Thread.currentThread().getName()+"\tsendSms");
        sendEmail();
    }
    public synchronized void sendEmail() throws Exception{
        System.out.println(Thread.currentThread().getName()+"\tsendEmail");
    }

}
public class ReenterLockDemo {

    /**
     * t1 sendSms
     * t1 sendEmail
     * t2 sendSms
     * t2 sendEmail
     * @param args
     */
    public static void main(String[] args) {
        final Phone phone = new Phone();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    phone.sendSms();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    phone.sendSms();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"t2").start();
    }


}
/**
t1	sendSms
t1	sendEmail
t2	sendSms
t2	sendEmail
*/

自旋鎖

在這裏插入圖片描述

獨佔鎖(寫)/共享鎖(讀)/互斥鎖

在這裏插入圖片描述

/**
 * 資源類
 */
class MyCaChe {
    /**
     * 保證可見性
     */
    private volatile Map<String, Object> map = new HashMap<>();
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    /**
     * 寫
     *
     * @param key
     * @param value
     */
    public void put(String key, Object value) {
        reentrantReadWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t正在寫入" + key);
            //模擬網絡延時
            try {
                TimeUnit.MICROSECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t正在完成");
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }

    /**
     * 讀
     *
     * @param key
     */
    public void get(String key) {
        reentrantReadWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t正在讀取");
            //模擬網絡延時
            try {
                TimeUnit.MICROSECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t正在完成" + result);
        } finally {
            reentrantReadWriteLock.readLock().unlock();
        }
    }

    public void clearCaChe() {
        map.clear();
    }

}

/**
 * Description:
 * 多個線程同時操作 一個資源類沒有任何問題 所以爲了滿足併發量
 * 讀取共享資源應該可以同時進行
 * 但是
 * 如果有一個線程想去寫共享資源來  就不應該有其他線程可以對資源進行讀或寫
 * <p>
 * 小總結:
 * 讀 讀能共存
 * 讀 寫不能共存
 * 寫 寫不能共存
 * 寫操作 原子+獨佔 整個過程必須是一個完成的統一整體 中間不允許被分割 被打斷
 *
 **/
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCaChe myCaChe = new MyCaChe();
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCaChe.put(temp + "", temp);
            }, String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            int finalI = i;
            new Thread(() -> {
                myCaChe.get(finalI + "");
            }, String.valueOf(i)).start();
        }
    }
}
 

讀寫鎖

CountDownLatch/CyclicBarrier/Semaphore

CountDownLatch

讓一些線程阻塞直到另外一些完成後才被喚醒

CountDownLatch主要有兩個方法,當一個或多個線程調用await方法時,調用線程會被阻塞.其他線程調用countDown方法計數器減1(調用countDown方法時線程不會阻塞),當計數器的值變爲0,因調用await方法被阻塞的線程會被喚醒,繼續執行

關門案例

ublic class CountDownLatchDemo {
    public static void main(String[] args) throws Exception {
        closeDoor();

    }

   /**
     * 關門案例
     * @throws InterruptedException
     */
    private static void closeDoor() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t" + "上完自習");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t班長鎖門離開教室");
    }

  
} 

秦滅六國

public class CountDownLatchDemo {
    public static void main(String[] args) throws Exception {
        sixCountry();

    }

    /**
     * 秦滅六國 一統華夏
     * @throws InterruptedException
     */
    private static void sixCountry() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t" + "國,滅亡");
                countDownLatch.countDown();
            }, CountryEnum.forEach(i).getName()).start();
        }
        countDownLatch.await();
        System.out.println("秦統一");
    }

  

CyclicBarrier

CyclicBarrier的字面意思是可循環(Cyclic) 使用的屏障(barrier).它要做的事情是,讓一組線程到達一個屏障(也可以叫做同步點)時被阻塞,知道最後一個線程到達屏障時,屏障纔會開門,所有被屏障攔截的線程纔會繼續幹活,線程進入屏障通過CyclicBarrier的await()方法.

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
            System.out.println("召喚神龍");
        });

        for (int i = 1; i <=7; i++) {
            final int temp = i;
            new Thread(()->{
             System.out.println(Thread.currentThread().getName()+"\t 收集到第"+ temp +"顆龍珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

Semaphore

信號量的主要用戶兩個目的,一個是用於多核共享資源的相互排斥使用,另一個用於併發資源數的控制.

搶車位

    public static void main(String[] args) {
        //模擬3個停車位
        Semaphore semaphore = new Semaphore(3);
        //模擬6部汽車
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                try {
                    //搶到資源
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "\t搶到車位");
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\t 停3秒離開車位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //釋放資源
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}

線程池,對ThreadPoolExecutor的理解

爲什麼使用線程池,優勢

線程池做的工作主要是控制運行的線程的數量,處理過程中將任務加入隊列,然後在線程創建後啓動這些任務,如果先生超過了最大數量,超出的數量的線程排隊等候,等其他線程執行完畢,再從隊列中取出任務來執行.

他的主要特點爲:線程複用:控制最大併發數:管理線程.

第一:降低資源消耗.通過重複利用自己創建的線程降低線程創建和銷燬造成的消耗.
第二: 提高響應速度.當任務到達時,任務可以不需要等到線程和粗昂就愛你就能立即執行.
第三: 提高線程的可管理性.線程是稀缺資源,如果無限的創阿金,不僅會消耗資源,還會較低系統的穩定性,使用線程池可以進行統一分配,調優和監控.

線程池如何使用

架構實現

Java中的線程池是通過Executor框架實現的,該框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor這幾個類.
在這裏插入圖片描述

API

Executors.newCachedThreadPool();

Executors.newWorkStealingPool(int);

java8新增,使用目前機器上可以的處理器作爲他的並行級別

Executors.newFixedThreadPool(int)
在這裏插入圖片描述
主要特點如下:
1.創建一個定長線程池,可控制線程的最大併發數,超出的線程會在隊列中等待.
2.newFixedThreadPool創建的線程池corePoolSize和MaxmumPoolSize是 相等的,它使用的的LinkedBlockingQueue

Executors.newSingleThreadExecutor()
在這裏插入圖片描述
主要特點如下:
創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務都按照指定順序執行.
newSingleThreadExecutor將corePoolSize和MaxmumPoolSize都設置爲1,它使用的的LinkedBlockingQueue

Executors.newCachedThreadPool()
在這裏插入圖片描述
主要特點如下:
創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則創建新線程.
newCachedThreadPool將corePoolSize設置爲0 MaxmumPoolSize設置爲Integer.MAX_VALUE,它使用的是SynchronousQUeue,也就是說來了任務就創建線程運行,如果線程空閒超過60秒,就銷燬線程

線程池幾個重要參數介紹

在這裏插入圖片描述
corePoolSize:線程池中的常駐核心線程數

在創建了線程池後,當有請求任務來之後,就會安排池中的線程去執行請求任務,近視理解爲今日當值線程
當線程池中的線程數目達到corePoolSize後,就會把到達的任務放入到緩存隊列當中.

maximumPoolSize:線程池能夠容納同時執行的最大線程數,此值大於等於1

keepAliveTime:多餘的空閒線程存活時間,當空間時間達到keepAliveTime值時,多餘的線程會被銷燬直到只剩下corePoolSize個線程爲止

默認情況下:
只有當線程池中的線程數大於corePoolSize時keepAliveTime纔會起作用,知道線程中的線程數不大於corepoolSIze,

unit:keepAliveTime的單位

workQueue:任務隊列,被提交但尚未被執行的任務

threadFactory:表示生成線程池中工作線程的線程工廠,用戶創建新線程,一般用默認即可

handler:拒絕策略,表示當線程隊列滿了並且工作線程大於等於線程池的最大顯示 數(maxnumPoolSize)時如何來拒絕.

線程池的底層工作原理

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

線程池的拒絕策略

等待隊列也已經排滿了,再也塞不下新的任務了
同時,線程池的max也到達了,無法接續爲新任務服務
這時我們需要拒絕策略機制合理的處理這個問題.

JDK內置的拒絕策略

AbortPolicy(默認):直接拋出RejectedException異常阻止系統正常運行

CallerRunPolicy:"調用者運行"一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是

DiscardOldestPolicy:拋棄隊列中等待最久的任務,然後把當前任務加入隊列中嘗試再次提交

DiscardPolicy:直接丟棄任務,不予任何處理也不拋出異常.如果允許任務丟失,這是最好的拒絕策略

以上內置策略均實現了RejectExecutionHandler接口

工作中單一的/固定數的/可變你的三種創建線程池的方法,那個用的多

答案是一個都不用,我們生產上只能使用自定義的

Executors中JDK給你提供了爲什麼不用

參考阿里巴巴java開發手冊

【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。 說明:使用線程池的好處是減少在創建和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。

【強制】線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。說明:Executors返回的線程池對象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允許的請求隊列長度爲Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM。
2)CachedThreadPool和ScheduledThreadPool:允許的創建線程數量爲Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。
在這裏插入圖片描述

線程池Demo

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                1L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<Runnable>(3),
                Executors.defaultThreadFactory(),
                //默認拋出異常
                //new ThreadPoolExecutor.AbortPolicy()
                //回退調用者
                //new ThreadPoolExecutor.CallerRunsPolicy()
                //處理不來的不處理
                //new ThreadPoolExecutor.DiscardOldestPolicy()
                new ThreadPoolExecutor.DiscardPolicy()
        );
        //模擬10個用戶來辦理業務 沒有用戶就是來自外部的請求線程.
        try {
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 辦理業務");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
        //threadPoolInit();
    }

    private static void threadPoolInit() {
        /**
         * 一池5個處理線程
         */
        //ExecutorService threadPool= Executors.newFixedThreadPool(5);
        /**
         * 一池一線程
         */
        //ExecutorService threadPool= Executors.newSingleThreadExecutor();
        /**
         * 一池N線程
         */
        ExecutorService threadPool = Executors.newCachedThreadPool();
        //模擬10個用戶來辦理業務 沒有用戶就是來自外部的請求線程.
        try {
            for (int i = 1; i <= 20; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 辦理業務");
                });
                try {
                    TimeUnit.MICROSECONDS.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}
 

如何合理配置線程池

CPU密集型

System.out.println(Runtime.getRuntime().availableProcessors());查看CPU核數
在這裏插入圖片描述

IO密集型在這裏插入圖片描述

在這裏插入圖片描述

死鎖編碼及定位分析

在這裏插入圖片描述

產生死鎖的主要原因

系統資源不足

進程運行推進的順序不合適

資源分配不當

死鎖demo

class HoldThread implements Runnable {

    private String lockA;
    private String lockB;

    public HoldThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA) {
            System.out.println(Thread.currentThread().getName() + "\t 自己持有鎖" + lockA + "嘗試獲得" + lockB);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName() + "\t 自己持有鎖" + lockB + "嘗試獲得" + lockA);
            }
        }
    }
}

/**
 * Description:
 * 死鎖是指兩個或者以上的進程在執行過程中,
 * 因爭奪資源而造成的一種相互等待的現象,
 * 若無外力干涉那他們都將無法推進下去
 **/
public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new HoldThread(lockA, lockB), "threadAAA").start();
        new Thread(new HoldThread(lockB, lockA), "threadBBB").start();
    }
}

解決思路

jps命令定位進程編號

jps -l 定位那個類
在這裏插入圖片描述

jstack找到死鎖查看

在這裏插入圖片描述
java -XX:+PrintCommandLineFlags -version

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