Java多線程 - 線程的使用

線程的建立

經過實現Runnable接口

class RunnableDemo implements Runnable {
    private String threadName;

    private RunnableDemo(String name) {
        this.threadName = name;
        System.out.println("creating thread:" + threadName);
    }

    @Override
    public void run() {
        System.out.println("Running " + threadName);

        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("Thread:" + threadName + "," + i);
                Thread.sleep(50);
            }
        } catch (InterruptedException e) {
            System.out.println("Thread " + threadName + "interrupter");
        }
        System.out.println("Thread " + threadName + " exiting");
    }
    // run
    public static void main(String[] args) {
        RunnableDemo r = new RunnableDemo("MyThread");
        r.run();
    }
}
複製代碼

經過繼承Thread類自己

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        System.out.println("thread" + Thread.currentThread().getId() + " running...");
    }
    // run 10 thread
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo[] threadDemos = new ThreadDemo[10];
        for (int i = 0; i < threadDemos.length; i++) {
            threadDemos[i] = new ThreadDemo();
        }
        for (ThreadDemo threadDemo : threadDemos) {
            threadDemo.start();
        }
        // wait other thread complete
        for (ThreadDemo threadDemo : threadDemos) {
            threadDemo.join();
        }
        System.out.println("completing");
    }
}
複製代碼

經過實現Callable建立線程(能夠對線程返回值處理)

經過FutureTask包裝一個Callable的實例,再經過Thread包裝FutureTask的實例,而後調用Threadstart()方法java

public class CallableDemo implements Callable {
    @Override
    public String call() throws Exception {
        return "yo!";
    }

    @Test
    public void callUse() throws Exception {
        CallableDemo callableDemo = new CallableDemo();
        System.out.println(callableDemo.call());
    }

    @Test
    public void threadUse() throws ExecutionException, InterruptedException {
        FutureTask futureTask= new FutureTask<>(new CallableDemo());
        Thread thread=new Thread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }
}
複製代碼

FutureTask繼承關係git

線程池執行線程

線程池的建立

通常經過ThreadPoolExecutor類來建立線程github

ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                          int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler
                          )
複製代碼
變量說明
  • corePoolSize 線程池的基本大小編程

  • maximumPoolSize 線程池的最大大小多線程

  • keepAliveTime 空閒線程(超出基本大小的線程)的存活時間併發

  • unit 空閒線程存活時間的單位(毫秒,秒...)ide

  • workQueue 任務隊列,提交的任務的阻塞隊列(BlockingQueue)。more: Java多線程 - 阻塞隊列詳解this

  • threadFactory 線程工產,線程的建立策略,有默認實現,能夠經過定製線程工廠來監聽線程信息spa

  • handler 飽和策略,當線程因爲任務隊列滿了,或者某個任務被提交到一個已被關閉的線程的處理方式線程

    • AbortPolicy 停止策略,默認策略 ,該策略會拋出RejectExecutionException異常,調用者能夠根據這個異常編寫本身的處理代碼

    • DiscardRunsPolicy 拋棄策略,悄悄拋棄該任務,不拋異常

    • DiscardOldestPolicy 拋棄最久任務策略 將工做隊列中最老的(也就是下一個要執行的)任務拋棄。優先隊列將會是優先級最高的

    • CallerRunsPolicy 調用者執行策略,將線程添加到添加工做隊列的線程去執行

ps: 構造器參考下表

繼承關係

線程池的使用

Runable接口
public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService pool=Executors.newFixedThreadPool(2);
        pool.execute(() -> System.out.println("yo!"));
        pool.shutdown();
}
複製代碼
Callable接口

經過調用submit方法

ExecutorService中提供了重載的submit()方法,該方法既能夠接收Runnable實例又能接收Callable實例。對於實現Callable接口的類,須要覆寫call()方法,而且只能經過ExecutorServicesubmit()方法來啓動call()方法

public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService pool=Executors.newFixedThreadPool(2);
        Future future=pool.submit(() -> {
            Thread.sleep(100);
            return "yo!";
        });
        System.out.println(future.get());
        pool.shutdown();
}
複製代碼
延時任務與週期任務的使用

定義:延時任務("在100ms後執行的任務") 週期任務("每10ms執行一次的任務")

使用:經過new ScheduledThreadPoolExector()對象

Demo:

public class ScheduleExecutorDemo implements Runnable {
    private String name;

    public ScheduleExecutorDemo(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(name + " 運行");
    }

    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService executorService1 = Executors.newScheduledThreadPool(2);
        // after 10s run
        executorService1.schedule(new ScheduleExecutorDemo("task1"), 10, TimeUnit.SECONDS);
        executorService1.shutdown();

        ScheduledExecutorService executorService2 = Executors.newScheduledThreadPool(2);
        // run per 1s
        executorService2.scheduleAtFixedRate(new ScheduleExecutorDemo("task1"), 
                0, 1, TimeUnit.SECONDS);
        // run per 2s
        executorService2.scheduleWithFixedDelay(new ScheduleExecutorDemo("task2"), 
                0, 2, TimeUnit.SECONDS);
    }
}
複製代碼
使用tips

來源:阿里巴巴Java開發手冊

  • 線程資源必須經過線程池提供,不容許在應用中自行顯式建立線程。

    說明: 使用線程池的好處是減小在建立和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。若是不使用線程池,有可能形成系統建立大量同類線程而致使消耗完內存或者「過分切換」的問題。

  • 線程池不容許使用 Executors 去建立,而是經過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同窗更加明確線程池運行規則,規避資源耗盡的風險。

    說明: Executors 返回的線程池對象的弊端以下:

    • FixedThreadPool 和 SingleThreadPool :容許的請求隊列長度爲 Integer.MAX_VALUE ,可能會堆積大量的請求,從而致使 OOM 。

    • CachedThreadPool 和 ScheduledThreadPool :容許的建立線程數量爲 Integer.MAX_VALUE ,可能會建立大量的線程,從而致使 OOM 。

  • 多線程並行處理定時任務時, Timer 運行多個 TimeTask 時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用 ScheduledExecutorService 則沒有這個問題。

Ref:

  1. JDK1.8.0 源碼

  2. Java多線程之Callable接口及線程池

  3. 《Java併發編程實戰》

  4. 《Java多線程編程實戰指南(核心篇)》

相關文章
相關標籤/搜索