Java多線程及併發知識點總結

目錄

 

一、volatile的理解

二、CAS的理解

三、ArrayList多線程異常:java.util.ConcurrentModificationException (同HashSet、HashMap問題)

四、公平鎖、非公平鎖、可重入鎖(遞歸鎖)、自旋鎖、讀/寫鎖(共享鎖/獨佔鎖)的理解

五、CountDownLatch、 CyclicBarrier、Semaphore的使用

六、阻塞隊列的理解

七、Synchronized和Lock的區別

八、Callable接口的理解

九、線程池Executor的理解

十、死鎖的理解


一、volatile的理解

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

1.1保證可見性

1.2不保證原子性-反JMM:數據加載過快,返回主內存數據覆蓋,導致數據丟失

1.3禁止指令重排

2.JMM(Java內存模型)理解

2.1可見性:主內存線程--->分配--->工作內存1、2、3線程...--->返回--->主內存線程

2.2原子性

2.3有序性

3.在哪些地方用到volatile

3.1單例模式DCL(Double Check Lock)代碼:DCL雙重檢查加鎖-可能在instance引用對象時,由於指令重排,導致初始化完成前被其他線程引用,造成線程不安全問題。

4.示例:

4.1非volatile 、synchronized解決多線程原子性問題:

JUC:java.util.concurrent.atomic包下提供了原子性方法,如:

AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.getAndIncrement();

4.2volatile單例模式DCL,禁止指令重排

private volatile static Instance ins = null;
public static Instance getInstance(){
    if (ins == null){
        synchronized (Instance.class){
            if (ins == null){
                ins = new Instance();
            }
        }
    }
    return ins;
}

二、CAS的理解

1.什麼是CAS

1.1compareAndSet--->比較並交換,期望相同即交換

2.CAS底層原理

2.1CAS方法

atomicInteger.compareAndSet(expect,update);

2.2調用Unsafe:Unsafe類的方法大部分爲native修飾(Java調用非Java代碼的接口),其直接調用內存偏移地址獲取數據。

unsafe.compareAndSwapInt(this, valueOffset, expect, update);

3.CAS的缺點

3.1由於沒有加鎖,循環時間長,開銷大

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

3.3會導致ABA問題:

①多線程時間差引起線程1A---(線程2A-B-C-D...-A)---B期間,線程2多次修改數據,導致線程1認爲數據未變化。

②適合只管結果不管過程的業務。

4.示例

4.1時間戳原子引用解決ABA問題:AtomicStamped...

public static void main(String[] args) {
        String val = "A";
        AtomicStampedReference<String> satf = new AtomicStampedReference<String>(val, 1);
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "初始版本號:" + satf.getStamp() + " val:" + satf.getReference());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            satf.compareAndSet("A", "B", satf.getStamp(), satf.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "修改後版本號:" + satf.getStamp() + " val:" + satf.getReference());
            satf.compareAndSet("B", "A", satf.getStamp(), satf.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "修改後版本號:" + satf.getStamp() + " val:" + satf.getReference());
        }, "t1").start();
        new Thread(() -> {
            int init = satf.getStamp();
            System.out.println(Thread.currentThread().getName() + "初始版本號:" + satf.getStamp());
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean flg = satf.compareAndSet("A", "B",init, satf.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "等待t1後版本號:" + satf.getStamp() + " cas狀態:" + flg + " val值:" + satf.getReference());
        }, "t2").start();
    }

三、ArrayList多線程異常:java.util.ConcurrentModificationException (同HashSet、HashMap問題)

1.1異常復現(犧牲Vector安全性,提高性能的結果)

List<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
    new Thread(() -> {
        list.add(UUID.randomUUID().toString().substring(0, 8));
        System.out.println(list);
    }, String.valueOf(i)).start();
}

1.2解決方法

①簡單解決:Collections.synchronized...

List<String> list = Collections.synchronizedList(new ArrayList<>());

②提升解決:JUC工具類--->CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap

如add方法,使用ReentrantLock加鎖實現

List<String> list = new CopyOnWriteArrayList<>();
 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();
        }
    }

四、公平鎖、非公平鎖、可重入鎖(遞歸鎖)、自旋鎖、讀/寫鎖(共享鎖/獨佔鎖)的理解

1.公平鎖和非公平鎖

1.1公平鎖:排隊佔鎖。

1.2非公平鎖:先搶佔鎖,搶不到排隊佔鎖。

Synchronized非公平鎖,ReentrantLock默認非公平鎖,可選擇鎖方式:

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

2.可重入鎖(遞歸鎖)

2.1同一線程進入外層鎖後,即不受限制進入內層鎖,以此避免死鎖的發生。

Synchronized、ReentrantLock爲可重入鎖。

    public synchronized void syc1() {
        syc2();        
    }
    public synchronized void syc2() {
    }

3.自旋鎖

3.1採用循環的方式去嘗試獲取鎖,以此防止出現阻塞,但是耗費CPU資源。

如:atomicInteger.getAndIncrement()--->unsafe.getAndAddInt(this, valueOffset, 1)就是自旋鎖;

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

4.讀/寫鎖(共享鎖/獨佔鎖)

4.1獨佔鎖:只被一個線程佔有;

4.2共享鎖:可被多個線程持有;

4.3解決Synchronized、ReentrantLock獨佔鎖的問題:

ReentrantReadWriteLock提供了讀寫分離的鎖機制,多線程操作時,寫操作就獨佔鎖,讀操作就共享鎖。

        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        try {
            rwLock.writeLock().lock();
            //寫操作
        }finally {
            rwLock.writeLock().unlock();
        }
        try {
            rwLock.readLock().lock();
        } finally {
            //寫操作
            rwLock.readLock().unlock();
        }

五、CountDownLatch、 CyclicBarrier、Semaphore的使用

1.CountDownLatch

1.1定義計數器,讓調用await()的線程阻塞,直到計數減爲0,再喚醒。

1.2示例

注:如果計數不夠,會一直處於等待,耗費資源。

        System.out.println("定義countDownLatch計數器");
        int cdNum = 6;
        CountDownLatch countDownLatch = new CountDownLatch(cdNum);
        for (int i = 0; i < cdNum; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "分線程結束,並計數");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        System.out.println(Thread.currentThread().getName() + "等待分線程全部結束後繼續主線程");
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "主線程結束");

2.CyclicBarrier

2.1定義計數器,直到計數加爲num,纔會執行await()後的方法。

2.2示例

        System.out.println("定義cyclicBarrier計數器");
        int num = 6;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
            System.out.println("分線程執行完畢,交回主線程");
        });
        for (int i = 0; i < num; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "分線程執行中");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }

3.Semaphore

3.1Semaphore用於多線程搶佔多個資源,及併發資源數的控制,Synchronized、ReentrantLock是多線程搶佔一個資源,也就是併發數爲1。

3.2示例

        System.out.println("定義semaphore信號量,進行多線程搶佔資源");
        int num = 3;
        Semaphore semaphore = new Semaphore(num);
        for (int i = 0; i < 9; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "  線程佔用資源中");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"  3秒後釋放資源");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }

六、阻塞隊列的理解

隊列:先到先得。

棧:先進後出。

1.1阻塞隊列,即隊列滿時能出不能進,隊列空時能進不能出。

1.2常用方法:

方法類型 拋出異常 特殊值 阻塞 超時
出入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() tkae() poll(time,unit)
檢查 element() peek() 不可用 不可用

 

1.3常用實現類:

SynchronousQueue:不存儲元素的阻塞隊列,只能put一個,take一個。

ArrayBlockingQueue

LinkedBlockingQueue

PriorityBlockingQueue
SynchronousQueue

 

1.4擴展示例:

public static void main(String[] args) throws Exception {
        MyResource myResource = new MyResource(new ArrayBlockingQueue<>(5));
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t生產線程啓動");
            try {
                myResource.myProd();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Prod").start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t消費線程啓動");
            try {
                myResource.myConsumer();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Cons").start();
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("時間到,停止活動");
        myResource.stop();
    }

    static class MyResource {
        /**
         * 默認開啓 進行生產消費的交互
         */
        private volatile boolean flag = true;
        /**
         * 默認值是0
         */
        private AtomicInteger atomicInteger = new AtomicInteger();
        private BlockingQueue<String> blockingQueue = null;

        public MyResource(BlockingQueue<String> blockingQueue) {
            this.blockingQueue = blockingQueue;
            System.out.println("接口實現方法\t" + blockingQueue.getClass().getName());
        }

        public void myProd() throws Exception {
            String data = null;
            boolean returnValue;
            while (flag) {
                data = atomicInteger.incrementAndGet() + "";
                returnValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
                if (returnValue) {
                    System.out.println(Thread.currentThread().getName() + "\t 插入隊列數據 " + data + " 成功");
                } else {
                    System.out.println(Thread.currentThread().getName() + "\t 插入隊列數據 " + data + " 失敗");
                }
                TimeUnit.SECONDS.sleep(1);
            }
            System.out.println(Thread.currentThread().getName() + "\t 停止 表示 flag" + flag);
        }

        public void myConsumer() throws Exception {
            String result = null;
            while (flag) {
                result = blockingQueue.poll(2L, TimeUnit.SECONDS);
                if (StringUtils.isBlank(result)) {
                    flag = false;
                    System.out.println(Thread.currentThread().getName() + "\t 超過2m沒有取到 消費退出");
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "\t 消費隊列 " + result + " 成功");
            }
        }

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

七、Synchronized和Lock的區別

1.Synchronized是關鍵字,屬於JVM層面;Lock是JUC的具體類,是API層面的鎖。

2.Synchronized不需要手動釋放資源;Lock需要手動釋放,用lock和unlock配合使用,否則導致死鎖。

3.Synchronized是不能被中斷的;Lock是可以中斷的。

4.Synchronized是非公平鎖;Lock默認非公平鎖,設爲true即爲公平鎖。

5.Synchronized隨機喚醒鎖;Lock可以精準喚醒鎖。

Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
c1.await();
c1.signal();

八、Callable接口的理解

1.創建線程的四種方式:

①直接繼承Thread。

②實現Runnable接口:無返回結果,不拋異常。

③實現Callalbe接口:有返回結果,拋異常。

④線程池Executor。

2.Callable接口可以獲取執行結果,並利用阻塞一直等待結果返回。

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> oft = new FutureTask<>(new MyThread());
        new Thread(oft, "1").start();
        int num1 = 100;
        System.out.println(Thread.currentThread().getName() + "\t 獲取Callable線程結果,未完成則堵塞等待");
        int num2 = oft.get();
        System.out.println(Thread.currentThread().getName() + "\t 處理結果: \t" + (num1 + num2));
    }
    static class MyThread implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            System.out.println(Thread.currentThread().getName() + "\t Callable進行處理中!");
            TimeUnit.SECONDS.sleep(3);
            return 100;
        }
    }

九、線程池Executor的理解

1.線程池優勢:通過線程的複用,管理線程併發數,最大化利用系統資源,提供響應速度。

2.常見實現方法:

Executors.newFixedThreadPool 創建定長線程池,使用LinkedBlockingQueue阻塞隊列
Executors.newSingleThreadPool 創建一個線程的線程池,使用LinkedBlockingQueue阻塞隊列
Executors.newCachedThreadPool 創建可調節的線程池,使用SynchronousQueue阻塞隊列
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();
//        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            threadPool.execute(()->{
                System.out.println(Thread.currentThread().getName() + "\t 進入業務處理");
            });
        }
        threadPool.shutdown();
    }

3.線程池參數說明

--->public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
--->public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue
                          ) {
    this(corePoolSize,------------------------>核心線程數
         maximumPoolSize,--------------------->最大線程數 
         keepAliveTime, ---------------------->空閒線程存活時間,最大線程數減爲核心線程數
         unit,-------------------------------->空閒線程存活時間d單位
         workQueue,--------------------------->任務隊列,被提交但未被執行的任務
         Executors.defaultThreadFactory(), --->線程工廠
         defaultHandler);}-------------------->拒絕策略,最大線程數和任務隊列都滿的時候執行拒絕策略

4.拒絕策略說明

AbortPolicy 默認策略,直接拋出RejectedExecution異常
CallerRunsPolicy 將任務回退到調用者處理,從而降低新任務的流量
DiscardOldestPolicy 將隊列中等待最久的任務拋棄,並提交當前任務
DiscardPolicy 直接丟棄當前任務

5.生產實現策略

5.1線程池使用ThreadPoolExecutor的方式創建。

5.2最大線程5+阻塞隊列5<小於任務15,執行決絕策略。

5.3簡單配置:

①CPU密集型任務線程數=CPU核數+1;

②  IO密集型任務線程數=CPU核數*2   或  CPU核數/(1-阻塞系統)

阻塞係數≈0.8 - 0.9

    public static void main(String[] args) {
        int cpuNum = Runtime.getRuntime().availableProcessors();
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                1L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(5),
                Executors.defaultThreadFactory()
//                new ThreadPoolExecutor.AbortPolicy()
//                new ThreadPoolExecutor.CallerRunsPolicy()
//                new ThreadPoolExecutor.DiscardOldestPolicy()
//                new ThreadPoolExecutor.DiscardPolicy()
        );
        for (int i = 0; i < 15; i++) {
            threadPool.execute(()->{
                System.out.println(Thread.currentThread().getName()+"\t 進入業務處理");
            });
        }
        threadPool.shutdown();

十、死鎖的理解

1.死鎖即多個線程相互等待的現象,由系統資源不足,進程運行推進的順序不合適,資源分配不當導致。

2.示例:

    public static void main(String[] args) {
        String lock1 = "lock1";
        String lock2 = "lock2";
        new Thread(new DeadLock(lock1, lock2), "A").start();
        new Thread(new DeadLock(lock2, lock1), "B").start();
    }

    static class DeadLock implements Runnable {
        private String val1;
        private String val2;

        public DeadLock(String val1, String val2) {
            this.val1 = val1;
            this.val2 = val2;
        }

        @Override
        public void run() {
            synchronized (val1) {
                System.out.println(Thread.currentThread().getName() + "\t 當前持有" + val1 + "\t 準備獲取" + val2);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (val2) {
                    System.out.println(Thread.currentThread().getName() + "\t 已獲取" + val2);
                }
            }
        }
    }

3.如何排查是否發生死鎖

① 查詢進程號

jps -l 

② 得到Java stack information for the threads listed above,即死鎖發生的具體信息

stack 進程號

 

 

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