Java基礎知識復盤-多線程

一、Java基礎知識復盤-多線程

1.1.線程的創(chuàng)建和使用

方式一:繼承Thread類

  • 創(chuàng)建一個類繼承于Thread
  • 重寫Thread類的run方法
  • 創(chuàng)建繼承Thread類的子類對象
  • 通過子類對象調(diào)用start()方法
public class ThreadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 0; i < 1000; i++) {
            if (i % 2 == 0) {
                System.out.println("我是主線程,求偶數(shù):" + i);
            }
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            if (i % 2 != 0) {
                System.out.println("我是子線程,求奇數(shù):" + i);
            }
        }
    }
}

方式二:實現(xiàn)Runnable接口

  • 創(chuàng)建一個實現(xiàn)了Runnable接口的類。
  • 實現(xiàn)接口中定義的run()抽象方法。
  • 創(chuàng)建一個實現(xiàn)類的對象。
  • 將實現(xiàn)類的對象通過參數(shù)的形式傳遞到Thread的構造方法中,以此來創(chuàng)建Thread對象。
  • 通過Thread對象來調(diào)用start()方法。
public class RunnableTest {
    public static void main(String[] args) {
        MyRunnableThread myRunnableThread = new MyRunnableThread();
        Thread thread = new Thread(myRunnableThread);
        thread.start();
    }
}

class MyRunnableThread implements Runnable {
    @Override
    public void run() {
        Thread.currentThread().setName("子線程");
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

方式三:實現(xiàn)Callable接口

  • 創(chuàng)建一個實現(xiàn)類Callable接口的實現(xiàn)類。
  • 重寫call()方法。
  • 創(chuàng)建實現(xiàn)類的對象。
  • 將實現(xiàn)callable接口的實現(xiàn)類對象作為參數(shù)傳遞到FutureTask的構造函數(shù)中。
  • 創(chuàng)建一個Thread對象,并將FutureTask對象作為參數(shù)傳遞,并調(diào)用start()方法。
  • 如果需要獲取線程的返回值,就使用get()方法調(diào)用。
public class CallableTest {
    public static void main(String[] args) {
        CallableThread callableThread = new CallableThread();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callableThread);
        new Thread(futureTask).start();
        try {
            Integer sum = futureTask.get();
            System.out.println(sum);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class CallableThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 1000; i++) {
            sum += i;
        }
        return sum;
    }
}

對比方式二,有什么不同

  • 相比run()方法,可以有返回值。
  • 這個方法可以拋出異常,實現(xiàn)Runnable接口的run方法只能try-catch。
  • 支持泛型的返回值。
  • 可以借助FutureTask類,獲取返回結果等操作。

方式四:使用線程池

  • 提供指定線程數(shù)量的線程池
  • 執(zhí)行指定的線程操作,需要提供實現(xiàn)類Runnable接口或者Callable接口的實現(xiàn)類對象
public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Future submit = executorService.submit(new MyThreadPool());
    }
}

class MyThreadPool implements Callable {

    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            sum += i;
        }
        return sum;
    }
}

說明:

ExecutorService是真正的線程池接口,常見的子類有ThreadPoolExcutor。

Executors是一個工具類,用于創(chuàng)建不同的線程池,如下

  • Executors.newCachedThreadPool();:創(chuàng)建一個可根據(jù)需要創(chuàng)建新線程的線程池。主要問題是線程數(shù)最大數(shù)是Integer.MAX_VALUE,可能會創(chuàng)建數(shù)量非常多的線程,甚至OOM。
  • Executors.newFixedThreadPool(10);:創(chuàng)建一個可重用固定線程數(shù)的線程池。 主要問題是堆積的請求處理隊列可能會耗費非常大的內(nèi)存,甚至OOM。
  • Executors.newSingleThreadExecutor();:創(chuàng)建一個只有一個線程的線程池。 主要問題是堆積的請求處理隊列可能會耗費非常大的內(nèi)存,甚至OOM。
  • Executors.newScheduledThreadPool(10);:創(chuàng)建一個線程池,可以在定期執(zhí)行。主要問題是線程數(shù)最大數(shù)是Integer.MAX_VALUE,可能會創(chuàng)建數(shù)量非常多的線程,甚至OOM。

尷尬的是,線程池不建議使用Executors去創(chuàng)建,而是通過ThreadPoolExecutor的方式。如下所示:

public class ThreadPoolFactoryTest {
    private static ThreadFactory myThreadFactory = new ThreadFactoryBuilder().setNameFormat("Mythread-pool-%d").build();

    private static ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(10, 30, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(), myThreadFactory);

    public static void main(String[] args) {
        myThreadPool.execute(new MyThread2());
    }
}

class MyThread2 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

Thread類的常用方法

  • start():啟動當前線程,并調(diào)用run()方法。
  • Thread.currentThread():得到當前線程。
  • Thread.currentThread().getName();:得到當前線程名稱。
  • Thread.currentThread().setName("");:給當前線程設置名稱。
  • yield();:釋放當前CPU執(zhí)行權,也可能釋放后又被自己搶到。
  • join():就是在線程A中調(diào)用線程B的join()方法,就是線程A進入阻塞 狀態(tài),等線程B 全結束后再繼續(xù)執(zhí)行。
  • sleep(2000):當前線程睡眠時間。也就是阻塞指定時間。
  • isAlive():判斷當前線程是否處于存活狀態(tài)。
  • setPriority(Thread.MAX_PRIORITY);:設置線程的優(yōu)先級,優(yōu)先級高只是說優(yōu)先概念執(zhí)行,并不是一定比優(yōu)先級低的先執(zhí)行。高優(yōu)先級的線程比低優(yōu)先級線程搶得CPU的概率要高一點。

1.2.線程的生命周期

線程的生命周期狀態(tài)定義在Thread.State枚舉類中,NEW、RUNNABLEBLOCKED、WAITINGTIMED_WAITING``TERMINATED。簡單的將一個線程的狀態(tài)有:新建、就緒、運行、阻塞和死亡五種狀態(tài)。

生命周期圖解

image-20190402234652085

1.3.線程的同步

線程的同步就是為類解決線程安全問題,線程安全問題就是程序在單線程和多線程分別執(zhí)行后,結果不一樣。這就出現(xiàn)了線程不安全。

synchronized關鍵字

使用synchronized關鍵字,同步代碼塊,將操作共享數(shù)據(jù)的代碼放在同步代碼塊中,共享數(shù)據(jù)就是指多個線程共同操作的變量。同步監(jiān)視器就是鎖,任何一個類的對象都可以充當鎖,要求多個線程共用同一把鎖。語法如下:

synchronized (同步監(jiān)視器){

}
public class ThreadState {
    public static void main(String[] args) {
        TicketThread ticketThread1 = new TicketThread();
        Thread thread1 = new Thread(ticketThread1);
        thread1.setName("窗口一");
        thread1.start();
        Thread thread2 = new Thread(ticketThread1);
        thread2.setName("窗口二");
        thread2.start();
        Thread thread3 = new Thread(ticketThread1);
        thread3.setName("窗口三");
        thread3.start();
    }
}

class TicketThread implements Runnable {

    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (this ) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + ":買票,票號:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

1.4.線程的通信

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • ??一個任務通常就是一個程序,每個運行中的程序就是一個進程。當一個程序運行時,內(nèi)部可能包含了多個順序執(zhí)行流,每個順...
    OmaiMoon閱讀 1,804評論 0 12
  • 第一部分 來看一下線程池的框架圖,如下: 1、Executor任務提交接口與Executors工具類 Execut...
    壓抑的內(nèi)心閱讀 4,394評論 1 24
  • 轉自 http://blog.csdn.net/ChatHello/article/details/6906097...
    呂品?閱讀 5,516評論 0 100
  • 整理來自互聯(lián)網(wǎng) 1,JDK:Java Development Kit,java的開發(fā)和運行環(huán)境,java的開發(fā)工具...
    Ncompass閱讀 1,617評論 0 6
  • 那一年大概是高三還是好朋友念高四,還是我們都已經(jīng)在大學校園了,記不清了,反正我的高中同桌,跟著他的超級偶像張國榮一...
    靜工皮匠閱讀 283評論 0 1

友情鏈接更多精彩內(nèi)容