Java基礎——多線程

Java基礎-多線程

多個線程一塊兒作同一件事情,縮短期,提高效率
提升資源利用率
加快程序響應,提高用戶體驗java

建立線程

1. 繼承Thread類

  • 步驟
    • 繼承Thread類,重寫run方法
    • 調用的時候,直接new一個對象,而後調start()方法啓動線程
  • 特色
    • 因爲是繼承方式,因此不建議使用,由於Java是單繼承的,不夠靈活
    • Thread類本質也是實現Runnable接口(public class Thread implements Runnable)

  

2. 實現Runnable接口

  • 步驟
    • 實現Runnable接口,重寫run()方法
    • 建立Runnable實現類的實例,並用這個實例做爲Thread的target來建立Thread對象
    • 調用Thread類實例對象的start()方法啓動線程
  • 特色
    • 只是實現,保留了繼承的其餘類的能力
    • 若是須要訪問當前線程,必須使用Thread.currentThread()方法

  

3. 實現 Callable接口

  • 步驟
    • 實現Callable接口,重寫call()方法。
    • 建立Callable實現類的實例,使用FutureTask類來包裝Callable對象
    • 並用FutureTask實例做爲Thread的target來建立Thread對象
    • 調用Thread類實例對象的start()方法啓動線程
    • 調用FutureTask類實例對象的get()方法獲取異步返回值
  • 特色
    • call方法能夠拋出異常
    • 只是實現,保留了繼承的其餘類的能力
    • 若是須要訪問當前線程,必須使用Thread.currentThread()方法
    • 經過FutureTask對象能夠了解任務執行狀況,可取消任務的執行,還可獲取執行結果

  

4. 匿名內部類實現

  • 說明
    • 本質仍是前面的方法,只是使用了匿名內部類來實現,簡化代碼
    • Callable接口之因此把FutureTask類的實例化寫出來,是由於須要經過task對象獲取返回值

  

參數傳遞

1. 經過構造方法傳遞數據

  經過前面的學習,能夠看到,無論何種建立對象的方式,都須要新創建實例,因此咱們能夠經過構造函數傳入參數,並將傳入的數據使用類變量保存起來緩存

  • 特色
    • 在線程運行以前,數據就已經傳入了
    • 使用構造參數,當參數較多時,使用不方便
    • 不一樣參數條件須要不一樣的構造方法,使得構造方法較多

2. 經過變量和方法傳遞數據

  在線程類裏面定義一些列的變量,而後定義set方法,在新建實例以後,調用set方法傳遞參數安全

  • 特色
    • 在參數較多時使用方便,按需傳遞參數

3. 經過回調函數傳遞數據

  使用線程方法本身產生的變量值做爲參數,去調取外部的方法,獲取返回數據的方式多線程

  • 特色
    • 擁有獲取數據的主動權
線程同步

  要跨線程維護正確的可見性,只要在幾個線程之間共享非 final 變量,就必須使用線程同步併發

1. ThreadLocal

  ThreadLocal利用空間換時間,經過爲每一個線程提供一個獨立的變量副本,避免了資源等待,解決了變量併發訪問的衝突問題,提升了併發量。實現了線程間的數據隔離,可是線程間沒法共享同一個資源異步

public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SyncTest syncTest = new SyncTest();
        ConcurrentHashMap<String, String> testConMap = new ConcurrentHashMap<>();
        for (int i = 0; i < 10; i++) {
            ThreadTest2 threadTest2 = new ThreadTest2();
            threadTest2.setSyncTest(syncTest);
            Thread threadTest = new Thread(threadTest2);
            threadTest.start();
        }
    }
}

//實現Runnable
class ThreadTest2 implements Runnable {
    private SyncTest syncTest;

    public void setSyncTest(SyncTest syncTest) {
        this.syncTest = syncTest;
    }

    @Override
    public void run() {
        syncTest.threadLocalTest(Thread.currentThread().getName());
    }
}

class SyncTest {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public void threadLocalTest(String name) {
        try {
            System.out.println(name + "進入了threadLocal方法!");
            threadLocal.set(name);
            Thread.currentThread().sleep(100);
            System.out.println(threadLocal.get() + "離開了threadLocal方法!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2. synchronized

  無論synchronized是用來修飾方法,仍是修飾代碼塊,其本質都是鎖定某一個對象。修飾方法時,鎖上的是調用這個方法的對象,即this;修飾代碼塊時,鎖上的是括號裏的那個對象ide

  • 特色
    • 鎖的對象越小越好
public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SyncTest syncTest = new SyncTest();
        ConcurrentHashMap<String, String> testConMap = new ConcurrentHashMap<>();
        for (int i = 0; i < 10; i++) {
            ThreadTest2 threadTest2 = new ThreadTest2();
            threadTest2.setSyncTest(syncTest);
            threadTest2.setTestConMap(testConMap);
            Thread threadTest = new Thread(threadTest2);
            threadTest.start();
        }
    }

}
//實現Runnable
class ThreadTest2 implements Runnable {
    private ConcurrentHashMap<String, String> testConMap;
    private SyncTest syncTest;

    public void setTestConMap(ConcurrentHashMap<String, String> testConMap) {
        this.testConMap = testConMap;
    }

    public void setSyncTest(SyncTest syncTest) {
        this.syncTest = syncTest;
    }

    @Override
    public void run() {
        //三個方法須要單獨測試,由於testConMap會相互影響

        //測試同步方法,鎖住的對象是syncTest
        //syncTest.testSyncMethod(testConMap,Thread.currentThread().getName());
        //測試同步代碼塊,鎖住的對象是testConMap
        //syncTest.testSyncObject(testConMap, Thread.currentThread().getName());
        //測試沒有鎖時執行請求是多麼的混亂!!!
        //syncTest.testNoneSyncObject(testConMap, Thread.currentThread().getName());
    }
}
//同步測試方法類
class SyncTest {
    public synchronized void testSyncMethod(ConcurrentHashMap<String, String> testConMap, String name) {
        try {
            System.out.println(name + "進入了同步方法!");
            testConMap.put("name", name);
            Thread.currentThread().sleep(10);
            System.out.println(testConMap.get("name") + "離開了同步方法!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void testSyncObject(ConcurrentHashMap<String, String> testConMap, String name) {
        synchronized (testConMap) {
            try {
                System.out.println(name + "進入了同步代碼塊!");
                testConMap.put("name", name);
                Thread.currentThread().sleep(10);
                System.out.println(testConMap.get("name") + "離開了同步代碼塊!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void testNoneSyncObject(ConcurrentHashMap<String, String> testConMap, String name) {
        try {
            System.out.println(name + "進入了無人管轄區域!");
            testConMap.put("name", name);
            Thread.currentThread().sleep(10);
            System.out.println(testConMap.get("name") + "離開了無人管轄區域!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3. volatile

  • 特色函數

    • 保證可見性,有序性,不保證原子性
    • 它會強制將對緩存的修改操做當即寫入主存
    • volatile不適合複合操做(對變量的寫操做不依賴於當前值),不然須要保證只有單一線程可以修改變量的值
    • 使用volatile關鍵字,能夠禁止指令重排序(單例雙重檢查鎖)
public class StudyThread {
    static int v = 1;//volatile可以保證變量的可見性

    public static void main(String[] args) {

        //改動線程
        new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    v++;//確保只有一個線程修改變量值
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        //檢測線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                int old = 0;
                while (old < 11) {
                    if (old != v) {
                        old = v;
                        System.out.println("檢測線程:v的值變更爲" + old);
                    }
                }
            }
        }).start();
    }
}

4. 線程安全的類

  Java中不少類說的線程安全指的是,它的每一個方法單獨調用(即原子操做)都是線程安全的,可是代碼整體的互斥性並不受控制學習

  • 線程安全的類有如下幾類測試

    • Concurrentxxx

    • ThreadPoolExecutor

    • BlockingQueue和BlockingDeque

    • 原子類Atomicxxx—包裝類的線程安全類

    • CopyOnWriteArrayList和CopyOnWriteArraySet

    • 經過synchronized 關鍵字給方法加上內置鎖來實現線程安全:Timer,TimerTask,Vector,Stack,HashTable,StringBuffer

    • Collections中的synchronizedCollection(Collection c)方法可將一個集合變爲線程安全:

      Map m=Collections.synchronizedMap(new HashMap());

線程池

線程池只能放入實現Runable或callable類線程,不能直接放入繼承Thread的類

1. Executors線程池的實現

  • 要點
    • 可能致使資源耗盡,OOM問題出現
    • 線程池不容許使用Executors去建立,而是經過ThreadPoolExecutor的方式(阿里巴巴java開發)
public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //建立一個線程池,該線程池重用固定數量的從共享無界隊列中運行的線程
        //ExecutorService threadPool = Executors.newFixedThreadPool(20);
        //建立一個維護足夠的線程以支持給定的並行級別的線程池,線程的實際數量能夠動態增加和收縮,工做竊取池不保證執行提交的任務的順序
        //ExecutorService threadPool = Executors.newWorkStealingPool(8);
        //建立一個使用從無界隊列運行的單個工做線程的執行程序。
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //建立一個根據須要建立新線程的線程池,但在可用時將從新使用之前構造的線程。若是沒有可用的線程,將建立一個新的線程並將其添加到該池中。未使用六十秒的線程將被終止並從緩存中刪除
        ExecutorService threadPool = Executors.newCachedThreadPool();
        //放入Runnable類線程
        for (int i = 0; i < 10; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("線程名:" + Thread.currentThread().getName());
                }
            });
        }
        //Thread.currentThread().sleep(1000);
        //放入Callable類線程
        List<Future<String>> futures = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<String> future = threadPool.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("線程名:" + Thread.currentThread().getName());
                    return Thread.currentThread().getName();
                }
            });
            futures.add(future);
        }
        threadPool.shutdown();
        for (Future future : futures) {
            System.out.println(future.get());
        }
    }
}

1. ThreadPoolExecutor建立線程池

  

  • 要點

    • 線程池空閒大小和最大線程數根據實際狀況肯定
    • keepAliveTime通常設置爲0
    • unit通常設置爲TimeUnit.SECONDS(其餘的也行,反正是0)
    • 任務隊列須要指定大小,不要使用無界隊列,容易形成OOM-> new ArrayBlockingQueue<>(512)
    • ThreadFactory threadFactory使用系統默認的
    • 拒絕策略:
      • AbortPolicy:拋出RejectedExecutionException(該異常是非受檢異常,要記得捕獲)
      • DiscardPolicy:什麼也不作,直接忽略
      • DiscardOldestPolicy:丟棄執行隊列中最老的任務,嘗試爲當前提交的任務騰出位置
      • CallerRunsPolicy:直接由提交任務者執行這個任務
public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        int poolSize = Runtime.getRuntime().availableProcessors() * 2;
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(512);
        RejectedExecutionHandler policy = new ThreadPoolExecutor.DiscardPolicy();
        ExecutorService executorService = new ThreadPoolExecutor(poolSize, poolSize,
                0, TimeUnit.SECONDS,
                queue,
                policy);
        //放入Runnable類線程
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("線程名:" + Thread.currentThread().getName());
                }
            });
        }

        //放入Callable類線程
        List<Future<String>> futures = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<String> future = executorService.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("線程名:" + Thread.currentThread().getName());
                    return Thread.currentThread().getName();
                }
            });
            futures.add(future);
        }
        for (Future future:futures) {
            System.out.println(future.get());
        }

        //放入Callable類線程
        //使用CompletionService簡化獲取結果的操做,執行完一個任務,獲取一個結果,結果順序和執行順序相同
        CompletionService<String> ecs = new ExecutorCompletionService<String>(executorService);
        for (int i = 0; i < 10; i++) {
            Future<String> future = ecs.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("線程名:" + Thread.currentThread().getName());
                    return Thread.currentThread().getName();
                }
            });
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(ecs.take().get());
        }
    }
}
相關文章
相關標籤/搜索