多線程高併發編程

什麼是進程和線程?

進程:在操作系統中能夠獨立運行,並且作爲資源分配的基本單位。它表示運行中的程序。系統運行一個程序就是一個進程從創建、運行到消亡的過程。

線程:是一個比進程更小的執行單位,能夠完成進程中的一個功能,也被稱爲輕量級進程。一個進程在其執行的過程中可以產生多個線程。

線程的生存週期

線程生存週期示意圖:

線程的幾種狀態:

1.新建(new):通過new新創建了一個線程對象。

2.就緒(Runable):線程對象創建後,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取cpu 的使用權 。

3.運行:可運行狀態(runnable)的線程獲得了cpu 時間片(timeslice) ,執行程序代碼

4.阻塞:阻塞狀態是指線程因爲某種原因放棄了cpu 使用權,也即讓出了cpu timeslice,暫時停止運行。直到線程進入可運行(runnable)狀態,纔有機會再次獲得cpu timeslice 轉到運行(running)狀態。阻塞的情況分三種

(一). 等待阻塞:運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。
(二). 同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池(lock pool)中。
(三). 其他阻塞:運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入可運行(runnable)狀態。

5.銷燬:線程run()、main() 方法執行結束,或者因異常退出了run()方法,則該線程結束生命週期。死亡的線程不可再次復生。

 

創建線程的四種方式?

一.繼承Thread類

package com.study.test;
 
/**
 * Create with IntelliJ IDEA.
 *
 * @author: [email protected]
 * Date: 2019/10/17
 * Time: 15:16
 */
public class Test {
    public static void main(String[] args) {
        //直接繼承了Thread ,創建一個Thread的實例
        ThreadTest t1 = new ThreadTest();
        t1.start();
        //如果是實現了接口的線程類,需要用對象的實例作爲Thread類構造方法的參數
        Thread t2 = new Thread(new RunableTest());
        t2.start();
    }
}
 
class ThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println("繼承了Thread類");
    }
}
class RunableTest implements Runnable{
 
    @Override
    public void run() {
        System.out.println("實現了Runnable類");
    }
}

 

二.實現Runable接口

 

三.實現Callable接口

package com.cecdata.test;

import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<String>{
    @Override
    public String call() throws Exception {
        for (int i=0 ;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"執行時間"+new Date().getTime()+"執行次數"+i);
        }
        return "callable執行完成";
    }

    public static void main(String[] args) {
        //實現callable創建一個線程
        //創建futureTask實例,創建callable實例
        FutureTask<String> futureTask = new FutureTask<String>(new MyCallable());
        //創建Thread實例,執行futureTask
        Thread thread = new Thread(futureTask,"myCallable");
        thread.start();
        //在主線程上打印信息
        for (int i=0 ;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"執行時間"+new Date().getTime()+"執行次數"+i);
        }
        //獲取並打印mycallable打印的結果
        try {
            String result = futureTask.get();
            System.out.println("MYcallable執行結果:"+ result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

實現接口和繼承Thread類比較

1.接口更適合多個相同的程序代碼的線程去共享同一資源

2.接口可以避免java的單繼承侷限性

3.接口代碼可以被多個線程共享,代碼和線程獨立

4.線程池只能放入runable和callable接口,不能直接放入thread的類

注:在Java中,每個程序啓動至少啓動2個線程。一個是main,一個是垃圾收集線程。

Runable和Callable接口比較

相同點:1.兩者都是接口

               2.兩者都可用來編寫多線程程序

               3.兩者都需要Thread.start()啓動線程

不同點:1.實現Callable接口的線程能返回結果,runable接口則不能

               2.callable接口call()方法允許拋出異常,runable接口的run()方法則不能

               3.callable接口可以調用Future.cancel取消執行,runable接口則不能取消

注:callable支持返回結果,此時調用futureTask.get()實現,此方法會阻塞主線程直接過去'將來'結果;當不調用此方法時,主線程不會阻塞。

四.線程池創建線程

爲什麼使用線程池?

多線程缺點:

處理任務的線程創建和銷燬,以及多線程之間的切換都非常耗時並消耗資源;

解決辦法:線程池

使用時線程已經存在,消除了線程的創建的時耗;

通過設置線程數目,防止資源不足。

ThreadPoolExecutor源碼

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

線程池類關係圖:

                     

          

java封裝了Executors類,方便我們瞭解創建四大線程池

1.Executors調用線程池創建線程

package com.cecdata.test;

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

public class MyRunabler implements  Runnable{
    @Override
    public void run() {
        for (int i=0 ;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"執行時間"+new Date().getTime()+"執行次數"+i);
        }
    }

    public static void main(String[] args) {
        //使用線程池創建線程
        //使用Executors 獲取線程池對象,
        // 固定線程個數
       ExecutorService executorService = Executors.newFixedThreadPool(10);
        //通過線程池對象創建線程並執行MyRunable實例
        executorService.execute( new MyRunabler());
        //在主線程上打印信息
        for (int i=0 ;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"執行時間"+new Date().getTime()+"執行次數"+i);
        }
    }
}
2.Executors.newFixedThreadPool(int); 一池固定數線程,newFixedThreadPool:用於執行長期的任務,性能好很多

實例:

public class MyNewFixedThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5個處理線程
        //  threadPool.execute(); 無返回值
        //  threadPool.submit(); 可以有返回值/無返回值
        try {
            //模擬10個用戶來辦理業務,每個用戶就是一個來自外部的請求線程
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+"\t 辦理業務");
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //關閉線程池
            threadPool.shutdown();
        }


    }
}

3.Executors.newSingleThreadExecutor(); 一池一線程,用於一個任務一個任務執行的場景

實例:

/**
 * newSingleThreadExecutor:用於一個任務一個任務執行的場景
 */
public class MyNewSingleThreadExecutorDemo {
    public static void main(String[] args) {
        //判斷cpu核數
        System.out.println(Runtime.getRuntime().availableProcessors());

    ExecutorService threadPools = Executors.newSingleThreadExecutor();//一池一線程
        try {
            //模擬10個用戶來辦理業務,每個用戶就是一個來自外部的請求線程
            for (int i = 1; i <= 10; i++) {
                threadPools.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+"\t 辦理業務");
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //關閉線程池
            threadPools.shutdown();
        }


    }

}

4.Executors.newCachedThreadPool(); 一池多線程,用於執行很多短期異步的小程序或者負載較輕的服務器

/**
 * newCachedThreadPool:用於執行很多短期異步的小程序或者負載較輕的服務器
 */
public class MyNewCachedThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();//一池多線程
        try {
            //模擬10個用戶來辦理業務,每個用戶就是一個來自外部的請求線程
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(Thread.currentThread().getName()+"\t 辦理業務");
                    }
                });
                //線程休息一會
                TimeUnit.SECONDS.sleep(5);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //關閉線程池
            threadPool.shutdown();
        }


    }
}

線程池七大參數:

 corePoolSize:核心線程的數量,默認不會被回收掉,但是如果設置了allowCoreTimeOut爲true,那麼當核心線程閒置時,也會被回收。
maximumPoolSize :最大線程數量,線程池能容納的最大容量
keepAliveTime:閒置線程被回收的時間限制,也就是閒置線程的存活時間
unit :keepAliveTime的單位,即保活時間的單位
workQueue :用於存放任務的隊列
threadFactory :創建線程的工廠類
handler:當任務執行失敗時,使用handler通知調用者,代表拒絕的策略

用生活的例子來解釋:去銀行辦理業務

線程池的工作原理示意圖:

 

如果還不明白的話:

  當調用線程池的 execute() 方法時,線程池會做出以下判斷:
  如果當前運行的線程小於線程池的核心線程數,那麼馬上創建線程完成這個任務。
  如果運行中的線程數大於等於線程池的核心線程數,那麼將線程放進任務隊列等待。
  如果此時任務隊列已滿,且正在運行的線程數小於最大線程數,立即創建非核心線程執行這個任務。
  如果此時任務隊列已滿,且正在運行的線程數等於最大線程數,則線程池會啓動飽和拒絕策略來執行。
  當線程空閒超過一定時間時,線程池會判斷當前運行線程數是否大於核心線程數,如果大於核心線程數,該線程就會被停掉,直至當前線程數等於核心線程數。

談談線程池的拒絕策略——四大拒絕策略:

拒絕策略是什麼?

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

四大拒絕策略??

CallerRunsPolicy:"調用者運行"一種調節機制,該策略不會拋棄任務,也不會拋棄一場,而是將某些任務退回到調用的線程 AbortPolicy(默認):直接拋出異常阻止系統正常運行

DiscardPolicy:直接丟棄任務,不予任何處理,不拋出異常,如果允許任務丟失,這是最好的一種方案。

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

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

超級大坑 一個都不用,我們生產中只能使用自定義的。

工作中如何使用線程池,是否自定義過線程池?

//自定義線程池
public final class MyExecutorThreadPoolDemo {
    private static MyExecutorThreadPoolDemo myExecutorThreadPoolDemo = new MyExecutorThreadPoolDemo();
    private static final int CORE_SIZE_POOL = 20;
    private static final int MAX_SIZA_POOL = 25;

    private static MyExecutorThreadPoolDemo newInstance() {
        return myExecutorThreadPoolDemo;
    }

    private final ExecutorService threadPool = new ThreadPoolExecutor(
            CORE_SIZE_POOL,
            MAX_SIZA_POOL,
            10L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(3),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.DiscardOldestPolicy());

    /**
     * 初始化線程池,並添加線程
     */
    public void perpare(Runnable task) {
        if (task != null) {
            threadPool.execute(task);
        }
    }

    /**
     * 關閉線程
     */
    public void shutDown(){
        threadPool.shutdown();
    }

}

 

我們公司直接使用底層的ThreadPoolExecutor進行封裝自定義線程池 

package com.hzz.cecdata.thread;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Create with IntelliJ IDEA.
 *
 * @author: [email protected]
 * Date: 2019/9/24
 * Time: 17:03
 */
public final class ThreadPoolManager {
    private static ThreadPoolManager threadPoolManager = new ThreadPoolManager();

    // 線程池維護線程的最少數量
    private static final int SIZE_CORE_POOL = 20;
    // 線程池維護線程的最大數量
    private static final int SIZE_MAX_POOL = 20;

    public static ThreadPoolManager newInstance() {
        return threadPoolManager;
    }

    /**
     * 線程池
     * @param corePoolSize - 池中所保存的線程數,包括空閒線程。
     * @param maximumPoolSize - 池中允許的最大線程數。
     * @param keepAliveTime - 當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間。
     * @param unit - keepAliveTime 參數的時間單位。
     * @param workQueue - 執行前用於保持任務的隊列。此隊列僅由保持 execute 方法提交的 Runnable 任務。
     * @param handler - 由於超出線程範圍和隊列容量而使執行被阻塞時所使用的處理程序。
     */
    private final ThreadPoolExecutor mThreadPool = new ThreadPoolExecutor(SIZE_CORE_POOL, SIZE_MAX_POOL, 0L,
            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

    /**
     * 初始化
     */
    public void perpare() {
        mThreadPool.setKeepAliveTime(10, TimeUnit.SECONDS);
        mThreadPool.allowCoreThreadTimeOut(true);
        if (mThreadPool.isShutdown() && !mThreadPool.prestartCoreThread()) {
            int startThread = mThreadPool.prestartAllCoreThreads();
        }
    }

    /**
     * 添加任務
     * @param task
     */
    public void addExecuteTask(Runnable task) {
        if (task != null) {
            mThreadPool.execute(task);
        }
    }

    /**
     * 判斷是否最後一個任務
     * @return
     */
    public boolean isTaskEnd() {
        if (mThreadPool.getActiveCount() == 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 獲取緩存大小
     * @return
     */
    public int getQueue(){
        return mThreadPool.getQueue().size();
    }

    /**
     * 獲取線程數量
     * @return
     */
    public int getPoolSize(){
        return mThreadPool.getPoolSize();
    }

    /**
     * 獲取已完成任務數
     * @return
     */
    public long getCompletedTaskCount(){
        return mThreadPool.getCompletedTaskCount();
    }

    /**
     * 關閉線程池
     */
    public void shutdown() {
        mThreadPool.shutdown();
    }

    private ThreadPoolManager() {}
}

線程安全問題

開啓線程後,那就涉及安全問題

安全即是數據安全,線程是什麼,是否安全,與我無關。但數據必須安全。這裏就要提到內存了,因爲,造成數據不安全的就是內存。

對於碼農來說:程序就是一個進程,一個線程是其中之一。當系統爲進程分配空間時,就會有公共空間(堆,公共方法區),和棧等。而造成不安全的就是這塊公共的內存空間。當一個線程在進行數據處理時而另一個線程也對此數據進行處理,數據不安全,程序紊亂,也就是說線程不安全。

線程安全的根本原因:

1.多個線程在操作共享數據

 

2多個線程對共享數據有寫操作

演出出現線程問題

package com.cecdata.test;

public class Ticket implements Runnable {
    private int ticketNum = 100;//電影票數量

    @Override
    public void run() {
        while (true) {
            if (ticketNum > 0) {
                //有票,線程睡眠100毫秒
                try {
                    Thread.sleep(100);
                    //打印當前售出票數字和線程名
                    String name = Thread.currentThread().getName();
                    //票數-1
                    System.out.println("線程name" + name + "銷售電影票:" + ticketNum--);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        //創建電影票
        Ticket ticket = new Ticket();
        //創建Thread對象,執行售賣電影票
        Thread thread1 = new Thread(ticket, "窗口1");
        Thread thread2 = new Thread(ticket, "窗口2");
        Thread thread3 = new Thread(ticket, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

執行結果出現線程安全問題:同一張票出現多個窗口進行販賣

  

解決線程安全問題

要解決上述線程問題,只要在某個線程修改共享資源的時候,其他線程不修改共享資源,等待修改完畢同步之後,才能去爭奪spu資源,完成對應操作,保證了數據的同步性,解決了線程安全問題

保證線程都能正常執行共享資源,java引入了7種線程同步機制

解決方法一:悲觀鎖

加鎖,只要加鎖,那就要產生線程的阻塞,執行的性能就會降低。悲觀鎖就是悲觀的認爲只要我不加鎖,那我的數據就會被其他線程修改,所以每次操作都要加鎖,直到操作完成。

悲觀鎖:

悲觀鎖就是假設每次操作都會有其他人使用同一個資源, 所以每次執行過程都是; 加鎖-->使用資源-->釋放鎖, 項目中常使用synchronized對需要的代碼部分加鎖。

使用synchronized主要是因爲synchronized使用的是內置鎖, 加鎖和解鎖都由jdk實現, 使用者無需手動控制, 比較方便。

使用場景多是多線程開發時, 並行處理數據, 對方法或者代碼塊使用。

在使用時, 可儘量減少synchronized修飾的方法或代碼塊中的代碼, 減少資源消耗。

 

具體悲觀鎖使用情況:

1.同步代碼塊(synchronized)

package com.cecdata.test;

public class Ticket implements Runnable {
    private int ticketNum = 100;//電影票數量

    @Override
    public void run() {
        while (true) {
         synchronized (this){
             if (ticketNum > 0) {
                 //有票,線程睡眠100毫秒
                 try {
                     Thread.sleep(100);
                     //打印當前售出票數字和線程名
                     String name = Thread.currentThread().getName();
                     //票數-1
                     System.out.println("線程name" + name + "銷售電影票:" + ticketNum--);

                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         }
        }
    }

    public static void main(String[] args) {
        //創建電影票
        Ticket ticket = new Ticket();
        //創建Thread對象,執行售賣電影票
        Thread thread1 = new Thread(ticket, "窗口1");
        Thread thread2 = new Thread(ticket, "窗口2");
        Thread thread3 = new Thread(ticket, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

2.同步方法(synchronized)

package com.cecdata.test;

public class Ticket implements Runnable {
    private int ticketNum = 100;//電影票數量

    @Override
    public void run() {
        while (true) {

            saveTicket();
        }
    }

    public synchronized void saveTicket() {
        if (ticketNum > 0) {
            //有票,線程睡眠100毫秒
            try {
                Thread.sleep(100);
                //打印當前售出票數字和線程名
                String name = Thread.currentThread().getName();
                //票數-1
                System.out.println("線程name" + name + "銷售電影票:" + ticketNum--);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        //創建電影票
        Ticket ticket = new Ticket();
        //創建Thread對象,執行售賣電影票
        Thread thread1 = new Thread(ticket, "窗口1");
        Thread thread2 = new Thread(ticket, "窗口2");
        Thread thread3 = new Thread(ticket, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

同步鎖是誰?

可以理解爲把需要的數據全部加鎖,在事務提交之前,這些數據全部讀寫互斥。

對於非static方法同步,同步鎖就是this,使用在方法上是,指的是當前對象;

對於static方法同步,同步鎖是當前方法所在類的字節碼對象(類名.class)。

3.同步鎖(ReentrantLock)

package com.cecdata.test;

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

public class Ticket implements Runnable {
    private int ticketNum = 100;//電影票數量
    private Lock lock = new ReentrantLock(true);//ture-公平鎖,多個線程擁有共同執行權;false-非公平,獨佔鎖

    @Override
    public void run() {
        while (true) {
            //加鎖
            lock.lock();
            if (ticketNum > 0) {
                //有票,線程睡眠100毫秒
                try {
                    Thread.sleep(100);
                    //打印當前售出票數字和線程名
                    String name = Thread.currentThread().getName();
                    //票數-1
                    System.out.println("線程name" + name + "銷售電影票:" + ticketNum--);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //釋放鎖
                    lock.unlock();
                }
            }
        }
    }


    public static void main(String[] args) {
        //創建電影票
        Ticket ticket = new Ticket();
        //創建Thread對象,執行售賣電影票
        Thread thread1 = new Thread(ticket, "窗口1");
        Thread thread2 = new Thread(ticket, "窗口2");
        Thread thread3 = new Thread(ticket, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

synchronized和lock的區別

synchronized是jvm層面的java內置關鍵字,Lock是java併發包的一個類

synchronized可以自動釋放鎖(線程執行完同步代碼塊釋放鎖,Lock線程執行過程中發生異常會釋放鎖),Lock需在finally中手動釋放鎖(unlock方法),否則造成線程死鎖

synchronized 關鍵字的兩個線程1,2,如果1獲取鎖,2等待,如果1阻塞,2一直等待;而Lock不一定等待,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了。

synchronized 的鎖可重入,不可中斷,非公平,而Lock鎖可重入,可判斷,可公平(可非)

synchronized 鎖適合代碼少的同步問題

synchronized 無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取鎖

Lock用於實現分組喚醒線程,可以精確喚醒(condition),而synchronized要麼隨機喚醒,要麼喚醒全部:代碼實例:

class ShareResource {

    private int number = 1;//A:1,B:2,C:3
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    //判斷
    public void print5() {
        lock.lock();
        try {
            while (number != 1) {
                condition1.await();
            }
            //2.幹活
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + i);
            }
            number = 2;
            //3.通知
            condition2.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print8() {
        lock.lock();
        try {
            while (number != 2) {
                condition2.await();
            }
            //2.幹活
            for (int i = 1; i <= 8; i++) {
                System.out.println(Thread.currentThread().getName() + i);
            }
            //3.通知
            number = 3;
            condition3.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print10() {
        lock.lock();
        try {
            while (number != 3) {
                condition3.await();
             }
            //2.幹活
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + i);
            }
            //3.通知
            number = 1;
            condition1.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

public class TestLockCondition {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResource.print5();
            }

        }, "A").start();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResource.print8();
            }

        }, "B").start();
        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                shareResource.print10();
            }

        }, "C").start();
    }

}

 

請手寫一個自旋鎖 ?

自旋鎖:是指嘗試獲取鎖的線程不會立即阻塞,而是採用循環的方式去嘗試獲取鎖, 這樣的好處時減少線程上下                                        文切換的消耗  ,缺點:消耗CPU

實例代碼:

/**
 * 自旋鎖:是指嘗試獲取鎖的線程不會立即阻塞,而是採用循環的方式去嘗試獲取鎖,
 * 這樣的好處時減少線程上下文切換的消耗,缺點:消耗CPU
 * 模擬衛生間
 * 編制一個自旋鎖
 */
public class SpinLockDemo {
    //原子引用線程
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    public void myLock(){
        Thread thread =Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t come to 0()0");
        while (!atomicReference.compareAndSet(null,thread)){

        }

    }
    public void myUnLock(){
        Thread thread =Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"\t invoke myUnlock");
    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();
        new Thread(()->{
            spinLockDemo.myLock();
            try {
                //暫停線程一會
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnLock();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            spinLockDemo.myLock();
            try {
                //暫停線程一會
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnLock();
        },"B").start();
    }
}

執行情況:

 

 何爲公平鎖和非公平鎖?兩者區別?

 公平鎖:

是指多個線程按照申請鎖的順序來獲取鎖,獲取鎖時先查看此鎖維護的等待隊列,如果爲空,或者當前線程時等待隊列的第一個,就佔有鎖  ,否則加入等待隊列中,類似排隊打飯,先來後到;

 非公平鎖:

是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖, 在高併發的情況下,有可能會造成優先級反轉或者飢餓現象

區別:

Lock lock = new ReentrantLock() 默認false非公平鎖,非公平鎖的優點在於吞吐量比公平鎖大, synchronized非公平鎖 

 

可重入鎖(又名遞歸鎖):

指的是同一線程外層函數獲取鎖之後,內層遞歸函數仍能獲取該鎖的代碼,在同一線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖,即 線程可以進入任何一個它已經擁有的鎖所同步着的代碼塊 , 比如:回家用鎖開過大門後,去有鎖衛生間,默認信任廁所鎖

 可重入鎖作用:避免死鎖 , reentrantlock/synchronized就是典型的可重入鎖

public class ReentrantLockDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();
        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();


        Thread t3 = new Thread(phone);
        Thread t4 = new Thread(phone);
        t3.start();
        t4.start();
    }

}


class Phone implements Runnable {
    public synchronized void sendSMS() throws Exception {
        System.out.println(Thread.currentThread().getId() + "\t invoked sendSms()");
        sendEmail();
    }

    public synchronized void sendEmail() throws Exception {
        System.out.println(Thread.currentThread().getId() + "\t invoked sendEmail()+++++++++++++++++++");
    }

    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        get();
    }

    public void get() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getId() + "\t invoked get()");
            set();
        } finally {
            lock.unlock();
        }
    }
//將兩次鎖能編譯成功,能運行,但是加幾次,解鎖幾次
    public void set() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getId() + "\t invoked set()");
        } finally {
            lock.unlock();
        }
    }
}

運行情況:

獨佔鎖(寫鎖):

         該鎖一次只能被一個線程持有,ReentrantLock 和 synchronized均爲獨佔鎖

共享鎖(讀鎖):

          指該鎖可被多個線程所持有

讀寫鎖(ReentrantReadWriteLock):

         對ReentrantReadWriteLock其讀鎖時共享鎖,其寫鎖爲獨佔鎖 ,讀鎖的共享鎖可以保證併發讀是非常高效的,讀寫,寫寫,寫讀的過程是互斥的

實例:不加讀寫鎖


/**
 * 資源類
 */
class MyCach {
    private volatile Map<String, Object> map = new HashMap<>();

   // private Lock lock = new ReentrantLock();//只允許一個線程操作,保證了原子性,但不能滿足併發性
    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "\t 正在寫入:" + key);
    //    lock.lock();
        try {
            //暫停線程一會
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
       //     lock.unlock();
        }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "\t 寫入完成:");

    }

    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "\t 正在讀取:" + key);
        try {
            //暫停線程一會
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object result = map.get(key);
        System.out.println(Thread.currentThread().getName() + "\t 讀取完成:" + result);

    }
}

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

執行結果爲:出現正在寫入時,被打斷了,違反了原子性

實例:加鎖之後



/**
 * 獨佔鎖:該鎖一次只能被一個線程持有,ReentrantLock 和 synchronized均爲獨佔鎖
 * 共享鎖:指該鎖可被多個線程所持有
 * 對ReentrantReadWriteLock其讀鎖時共享鎖,其寫鎖爲獨佔鎖
 * 讀鎖的共享鎖可以保證併發讀是非常高效的,讀寫,寫寫,寫讀的過程是互斥的
 */

 /*
  * 資源類
  *
  * 讀寫鎖Demo
  */
class MyCach1 {
    private volatile Map<String, Object> map = new HashMap<>();
    //private Lock lock = new ReentrantLock();只允許一個線程操作,保證了原子性,但不能滿足併發性
    private ReentrantReadWriteLock rwLork = new ReentrantReadWriteLock();

    public void put(String key, Object value) {

        rwLork.writeLock().lock();
        try {
            try {
                System.out.println(Thread.currentThread().getName() + "\t 正在寫入:" + key);
                //暫停線程一會
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 寫入完成:");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwLork.writeLock().unlock();
        }
    }

    public void get(String key) {
        rwLork.readLock().lock();
        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在讀取:" + key);
            try {
                //暫停線程一會
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 讀取完成:" + result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwLork.readLock().unlock();
        }

    }
}


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

執行結果::讀-讀能共存,讀-寫不共存,寫-寫不共存

//要了解:java.util.concurrentjava併發包

Semaphore

信號燈主要有兩個目的,一個是用於多個共享資源的互斥使用,另一個用於併發線程數的控制。

package com.hzz.cecdata.lock.semaphore;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 爭車位
 * 信號量主要有兩個目的,一個時用於多個共享資源的互斥使用,另一個用於併發線程數的控制
 */
public class SemaPhoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);//模擬3個車位
        for (int i = 1; i <= 6; i++) {//六部車
            final int tempInt = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();//佔
                        System.out.println(Thread.currentThread().getName() + "\t 搶到車位");
                        TimeUnit.SECONDS.sleep(3);
                        System.out.println(Thread.currentThread().getName() + "\t 停車3秒後離開車位");

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        //釋放
                        semaphore.release();
                    }

                }
            }, String.valueOf(i)).start();
        }
    }

}

CyclicBarrier :

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

package com.hzz.cecdata.lock.cyclic_barrier;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * 收集龍珠召喚神龍
 * CyclicBarrier 的字面意思時可循環使用的屏障。它要做的事情是,讓一組線程到達一個屏障(也可以叫做同步點)
 * 時被阻塞,直到最後一個線程到達屏障時,屏障纔會開門,所有被屏障攔截的線程纔會繼續幹活,線程進入屏障通過
 * CyclicBarrier的await方法。
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // CyclicBarrier(int parties, Runnable barrierAction)
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召喚神龍");
        });
        for (int i = 1; i <=7 ; i++) {
            final int tempInt = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"收集到第:"+tempInt+"龍珠");
                    try {
                        cyclicBarrier.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }
            },String.valueOf(i)).start();
        }
    }
}

CountDownLatch :

讓一些線程阻塞直到另一個線程完成一系列操作後才被喚醒,CountDownLatch主要有兩個方法,當一個或多個線程await方法時,調用線程會被阻塞。 其他線程調用countDown方法時會將計數器減1(調用countDown方法的線程不會被阻塞),  當計數器變爲0時,因調用await()方法被阻塞的線程會被喚醒,繼續執行。

舉例:使用CountDownLatch之前

public class CountDownLatchDemoBefore {
    public static void main(String[] args) {
        for (int i = 1; i <= 6; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t 上晚自習,離開教師");
                }
            }).start();
        }
        System.out.println(Thread.currentThread().getName() + "\t 班長離開教室,鎖門 ");
    }
}

執行結果:導致問題:班長把門鎖了,學生未出來,實際情況應當學生出來完,班長鎖門。

 

使用CountDownLatch之後



/**
 * 人離開完,纔會鎖門
 * CountDownLatch : 讓一些線程阻塞直到另一個線程完成一系列操作後才被喚醒
 * CountDownLatch主要有兩個方法,當一個或多個線程await方法時,調用線程會被阻塞。
 * 其他線程調用countDown方法時會將計數器減1(調用countDown方法的線程不會被阻塞),
 * 當計數器變爲0時,因調用await()方法被阻塞的線程會被喚醒,繼續執行。
 */
public class CountDownLatchDemo {
    public static void main(String[] args) {
        closeDoor();
    }

    public static void closeDoor() {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t 上晚自習,離開教師");
                    countDownLatch.countDown();
                }
            }).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "\t 班長離開教室,鎖門");
    }
}

執行結果:問題解決,學生離開之後,班長關門(班長關門一定要放在主線程,最後執行)

秦滅六國例子()

如何使用枚舉

package com.hzz.cecdata.lock.count_down_latch;

/**
 * 枚舉的使用
 * Created by 86175 on 2019/11/27.
 */
public enum CountryEnum {
    ONE(1, "齊"), TWO(2, "楚"), THREE(3, "韓"), FOUR(4, "燕"), FIVE(5, "魏"), SIX(6, "趙");
    private Integer retCode;
    private String retMessage;

    public Integer getRetCode() {
        return retCode;
    }

    public void setRetCode(Integer retCode) {
        this.retCode = retCode;
    }

    public String getRetMessage() {
        return retMessage;
    }

    public void setRetMessage(String retMessage) {
        this.retMessage = retMessage;
    }

    CountryEnum(Integer retCode, String retMessage) {
        this.retCode = retCode;
        this.retMessage = retMessage;
    }

    public static CountryEnum foreachCountryEnum(int index) {
        CountryEnum[] myArray = CountryEnum.values();
        for (CountryEnum countryEnum : myArray) {
            if (index == countryEnum.getRetCode()) {
                return countryEnum;
            }
        }
        return null;
    }
}
package com.hzz.cecdata.lock.count_down_latch;

import java.util.concurrent.CountDownLatch;

/**
 * 秦滅六國纔會統一
 * CountDownLatch : 讓一些線程阻塞直到另一個線程完成一系列操作後才被喚醒
 * CountDownLatch主要有兩個方法,當一個或多個線程await方法時,調用線程會被阻塞。
 * 其他線程調用countDown方法時會將計數器減1(調用countDown方法的線程不會被阻塞),
 * 當計數器變爲0時,因調用await()方法被阻塞的線程會被喚醒,繼續執行。
 */
public class CountDownLatchDemo2 {
    public static void main(String[] args) {
        closeDoor();
    }

    public static void closeDoor() {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t 國被滅");
                    countDownLatch.countDown();
                }
            }, CountryEnum.foreachCountryEnum(i).getRetMessage()).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "\t 秦國一統天下");
        //如何調用枚舉
        System.out.println(CountryEnum.FOUR);
    }
}
 

線程死鎖

什麼是線程死鎖?

多線程雖然提高了系統的處理能力,但是,併發執行也帶來了新的問題—死鎖

所謂死鎖指:多個線程因競爭資源而造成一種僵局,若無外力左右,進程無法繼續推進

死鎖產生的必要條件?

死鎖原因:
(1) 因爲系統資源不足。
(2) 進程運行推進的順序不合適。
(3) 資源分配不當等。

產生死鎖的四個必要條件:
(1) 互斥條件:一個資源每次只能被一個進程使用。
(2) 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。

 

死鎖代碼示例:前提是請求的資源是共享資源

package com.cecdata.test;

public class ThreadDeadLock implements Runnable {
    private int flag;//決定線程走向的標記
    private static Object object1 = new Object();//鎖對象1
    private static Object object2 = new Object();//鎖對象2
    public  ThreadDeadLock(int flag){
        this.flag =flag;
    }
    @Override
    public void run() {
        if (flag == 1) {
            //線程1執行
            synchronized (object1){
                System.out.println( Thread.currentThread().getName()+"已獲取到資源obj1,請求obj2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (object2){
                    System.out.println(Thread.currentThread().getName()+"已經獲取到obj1和obj2");
                }
            }
        }else {
            //線程2執行
            synchronized (object2){
                System.out.println( Thread.currentThread().getName()+"已獲取到資源obj2,請求obj1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (object1){
                    System.out.println(Thread.currentThread().getName()+"已經獲取到obj1和obj2");
                }
            }
        }
    }

    public static void main(String[] args) {
        //創建兩個ThreadDeadLock實例:falg=1;flag =2;
        ThreadDeadLock threadDeadLock1 = new ThreadDeadLock(1);
        ThreadDeadLock threadDeadLock2= new ThreadDeadLock(2);
        //創建兩個線程執行創建兩個ThreadDeadLock實例
        Thread thread1 = new Thread(threadDeadLock1,"threadDeadLock1");
        Thread thread2 = new Thread(threadDeadLock2,"threadDeadLock2");
        thread1.start();
        thread2.start();
    }
}

打印結果:

避免死鎖的方法:

1.加鎖順序:當多個線程要相同的一些鎖,但是按照不同的順序加鎖,死鎖的情況發生率較高,如果,程序運行能確保所有線程都是按照相同的順序去獲得鎖,那麼死鎖就不會發生。

 2.加鎖時限:加一個超時時間,若一個線程沒有在給定的時間內成功獲取所需的鎖,則進行回退操作,並釋放自己本身所持有的鎖,一段隨機時間之後,重新去獲取鎖。

3.死鎖檢測:死鎖檢測,每當線程去獲取鎖的時候,會在線程和鎖相關的數據結構中將其記下,除此之外,每當線程請求鎖,都需要記錄在數據結構中。死鎖檢測是一個死鎖避免機制。他主要針對的時那些不可能實現按序加鎖並且鎖超時也不可行的應用場景

線程通信

爲何要進行線程通信?

多個線程併發運行時,在默認情況下CPU是隨機切換線程的,有時我們希望CPU按我們自己的規律執行線程,此時需要線程直接協調通信。

線程通信的常用集中方式:

1.休眠喚醒方式

Object的notify ,notifyAll , wait

例子:利用線程打印10以內的奇偶數

package com.cecdata.test;
//使用object的notify();wait()喚醒和等待方法時必須放在synchronized方法和synchronized代碼快中
public class OddEvenDemo {
    private int i = 0;//要打印的數
    private Object object = new Object();

    //奇數打印方法,由奇數線程調用
    public void odd() {
        //判斷i是否小於10,小於10的進行打印
        while (i < 10) {
            synchronized (object) {
                if (i % 2 == 1) {
                    System.out.println("奇數:" + i);
                    i++;
                    //喚醒偶數線程打印
                    object.notify();
                } else {
                    try {
                        //等待偶數線程打印
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    //偶數打印方法,由偶數線程調用
    public void even() {
        while (i < 10) {
            synchronized (object) {
                //判斷i是否小於10,小於10的進行打印

                if (i % 2 == 0) {
                    System.out.println("偶數:" + i);
                    i++;
                    //喚醒奇數線程打印
                    object.notify();
                } else {
                    try {
                        //等待奇數線程打印
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }
    }

    public static void main(String[] args) {
        final OddEvenDemo oddEvenDemo = new OddEvenDemo();
        //開啓奇數線程打印
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                oddEvenDemo.odd();
            }
        });
        //開啓偶數線程打印
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                oddEvenDemo.even();
            }
        });
        //開啓線程
        thread1.start();
        thread2.start();
    }
}

使用condition實現線程等待喚醒方法

package com.cecdata.test.threadwait;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//     condition的await()和lock(互斥鎖/共享鎖)配合使用
public class OddEvenDemo2 {
    private int i = 0;//要打印的數
    private Lock lock = new ReentrantLock();
    //創建condition對象
    private Condition condition = lock.newCondition();

    //奇數打印方法,由奇數線程調用
    public void odd() {
        //判斷i是否小於10,小於10的進行打印
        while (i < 10) {
            //加鎖
            lock.lock();
            try {
                if (i % 2 == 1) {
                    System.out.println("奇數:" + i);
                    i++;
                    //喚醒偶數線程打印
                  condition.signal();
                } else {
                    try {
                        //等待偶數線程打印
                       condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }finally {
                //釋放鎖
                lock.unlock();
            }
        }
    }

    //偶數打印方法,由偶數線程調用
    public void even() {
        while (i < 10) {
            //加鎖
            lock.lock();
            try{
                //判斷i是否小於10,小於10的進行打印
                if (i % 2 == 0) {
                    System.out.println("偶數:" + i);
                    i++;
                    //喚醒奇數線程打印
                   condition.signal();
                } else {
                    try {
                        //等待奇數線程打印
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }finally {
                //釋放鎖
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        final OddEvenDemo2 oddEvenDemo2 = new OddEvenDemo2();
        //開啓奇數線程打印
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                oddEvenDemo2.odd();
            }
        });
        //開啓偶數線程打印
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                oddEvenDemo2.even();
            }
        });
        //開啓線程
        thread1.start();
        thread2.start();
    }
}

condition和object 進行線程喚醒,等待方法總結

      使用object的notify();wait()喚醒和等待方法時必須放在synchronized方法和synchronized代碼快中(同步鎖)

       object.wait()必須使用notify()方法喚醒

      condition的await()和lock(互斥鎖/共享鎖)配合使用

      condition的await()必須 signal()喚醒

 

java執行流程圖:

                                                               

java內存模型

jvm內存共分爲:虛擬機棧,堆,方法區,程序計數器,本地方法棧五個部分,結構圖如下:

                                          

 

pc程序計算器

java棧 JAVA Stask(虛擬機棧 JVM Stask)

方法區Method Area

                    

常量池

                    

java內存工作模型圖

多線程需要滿足以下特性

1.原子性

原子性,指一個操作或者多個操作,要麼全部執行,要麼全部不執行。

2.可見性

可見性是指當多個線程訪問同一變量時,一個線程修改了這個變量的值,其他線程能夠立刻看到修改的值。顯然,對於單線程,可見性不存在

3.有序性

程序執行得順序按照代碼得先後順序執行的。

---------------------------------------------------------

多線程控制類

爲了保持多線程的三個特性,jav引入多線程機制,介紹常用的幾種:

1.ThreadLocal:線程本地變量

     作用:

                  

      常用方法::副本創建方法,get:獲取副本方法,set:設置副本方法

       模擬線程轉賬:待續

2.原子類:保證變量原子操作

3.Lock類:保證線程有序性

    lock接口類關係圖:

                                   

                                            

         可重入鎖

            可重入鎖:即線程可以進入它已經擁有的鎖的同步代碼塊,則可以自動獲取鎖

            不可重入鎖:即線程請求它已經擁有的鎖時會阻塞

package com.cecdata.test;

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        for (int i = 0; i < 10; i++) {
            lock.lock();
            System.out.println("加鎖次數:" + i );
        }
        for (int i = 0; i < 10; i++) {

            try {
                System.out.println("解鎖次數:" + i);
            } finally {
                lock.unlock();
            }
        }
    }
}

讀寫鎖

讀寫鎖,即可以同時讀,讀的時候不能寫;不能同時寫;寫的時候不能讀

package com.cecdata.test;

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

public class ReadWriteLockDemo {
    private Map<String, String> map = new HashMap<>();//操作的map對象
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();//讀操作鎖
    private ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();//寫操作鎖

    public String get(String key) {
        readLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "讀操作已加鎖,開始讀操作");
            Thread.sleep(3000);
            return map.get(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            System.out.println(Thread.currentThread().getName() + "讀操作已解鎖,讀操作結束");
            readLock.unlock();
        }
    }

    public void put(String key, String value) {
        writeLock.lock();
        try {
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "寫操作已加鎖,開始寫操作");
            Thread.sleep(3000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + "寫操作已解鎖,寫操作結束");
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        final ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
        readWriteLockDemo.put("key1", "value1");
       new Thread("讀線程1"){
           public void run(){
               System.out.println(readWriteLockDemo.get("key1"));
           }
       }.start();
        new Thread("讀線程2"){
            public void run(){
                System.out.println(readWriteLockDemo.get("key1"));
            }
        }.start();
        new Thread("讀線程3"){
            public void run(){
                System.out.println(readWriteLockDemo.get("key1"));
            }
        }.start();


    }
}

 

4.Volatile關鍵字:保證線程變量可見性

 

 

 

 

 

 

四種常用的線程池

 

示例

package com.cecdata.test;

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

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final  int index =i;
            try {
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(index);
                }
            });
        }

    }
}

 

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