天天看點

閉關修煉(一)多線程線程與程序的差別多線程應用場景繼承方式建立多線程runnable接口建立多線程使用匿名内部類方式建立多線程Callable方式建立多線程線程池方式建立多線程多線程常見API守護線程與非守護線程多線程運作狀态join()方法作用案例多線程分批處理資料

2021年,記得元旦快了樂~~

文章目錄

  • 線程與程序的差別
  • 多線程應用場景
  • 繼承方式建立多線程
    • 同步和異步的概念?
      • 同步
      • 異步
  • runnable接口建立多線程
  • 使用匿名内部類方式建立多線程
  • Callable方式建立多線程
  • 線程池方式建立多線程
  • 多線程常見API
    • getId()
    • getName()
    • sleep()
    • Thread.currentThread()
    • stop()
    • Thread()
  • 守護線程與非守護線程
    • 設定守護線程
  • 多線程運作狀态
  • join()方法作用
    • 面試題順序執行T1,T2,T3
  • 案例多線程分批處理資料

線程與程序的差別

  • 什麼是應用程式?

    可以執行的軟體,在一個應用程式中,都會有一個程序。

  • 在程序中,程式代碼如何執行?

    在程序中,一定有一個主線程,java中的main

  • 什麼是線程?

    程序中的一條執行順序/流程/路徑。

  • 什麼是程序?

    程序中有多個不同的執行路徑,程序是線程的集合,是正在執行中的程式,任務管理器中可以看見程序。

  • 使用多線程的好處?

    提高程式效率。

    線程之間互不影響,因為線程都在自己獨立運作。

    一個程序可中有多個線程,提高程式效率,提高使用者體驗。

  • 注意事項

    多線程并不提高單路徑執行速度,而是多路徑同時進行提高程式效率。

多線程應用場景

  1. 多線程下載下傳
  2. 爬蟲
  3. ajax異步上傳
  4. 分布式job,同一時刻執行多個任務排程
  5. mq

繼承方式建立多線程

建立線程有5種方式

  1. 繼承Thread類
  2. runnable接口
  3. 使用匿名内部類方式
  4. callable
  5. 線程池

繼承Thread接口,重寫(override)run方法,run方法就是線程需要執行的代碼。

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i =0 ; i< 30; i++){
            System.out.println(i);
        }
    }
}
           

啟動線程,執行個體化類,調用start方法,如果調用run方法還是單獨的執行run方法。

public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i =31 ; i< 60; i++){
            System.out.println("main" + i);
        }
    }
           

啟動多線程,代碼不會從上往下執行。

同步和異步的概念?

同步

main函數中有方法①和方法②,每個方法都需要花費10秒時間,則需要20秒完成main函數。

代碼從上往下執行,被稱為同步。

同步中,main函數的方法之間存在依賴關系,存在執行順序,方法②等待方法①執行完畢後才能執行。

也叫做單線程。

異步

可以了解為多線程,方法①和方法②同時執行,每個方法都需要花費10秒時間,則需要10秒完成main函數。提高程式效率,線程間獨立,互不影響。

CPU排程時 異步中有執行權的概念,但在宏觀上看是同時進行的。

runnable接口建立多線程

建立類,實作runnable接口,重寫run方法

class MyThread2 implements Runnable{
    public void run() {
        for (int i =0 ; i< 30; i++){
            System.out.println("run" + i);
        }
    }
}
           

啟動線程方式,接口作為參數傳遞給Thread類,調用start方法

public static void main(String[] args) {
        MyThread2 myThread2 = new MyThread2();
        Thread thread = new Thread(myThread2);
        thread.start();
    }
           

使用繼承方式建立線程還是使用runnable接口建立線程好?

使用接口方式,開發面向接口程式設計,繼承後不能再繼承,接口可以。

使用匿名内部類方式建立多線程

什麼是匿名内部類?

沒有名稱的内部類。

例子:

abstract class Person{
    abstract void add();
}
           
public static void main(String[] args) {
        Person person = new Person() {
            void add() {
                System.out.println("hello");
            }
        };
        person.add();
    }
           

線程中也是類似,執行個體化Thread類,runnable接口使用匿名内部類進行實作

public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                System.out.println("Hello~");
            }
        });

        thread.start();
    }
           

Callable方式建立多線程

待更

線程池方式建立多線程

待更

多線程常見API

getId()

擷取線程的id,id唯一

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i =0 ; i< 30; i++){
            System.out.println(getId()+ "run" + i);
        }
    }
}
           

主線程id如何擷取?

主線程id為1

任何一個程式都會有一個主線程,本質也是一個線程。

getName()

擷取線程名稱

主線程的name是main

子線程名稱預設是Thread-0,Thread-1,Thread-2…

sleep()

暫停

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i =0 ; i< 30; i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getId()+ "run" + i);
        }
    }
}
           

Thread.currentThread()

擷取目前線程,在用Runnable接口的時候需要用這種方式擷取目前線程。

stop()

不安全,已過時

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i =0 ; i< 30; i++){
            if (i==5){
                stop();
            }
            System.out.println(getId()+ "run" + i);
        }
    }
}
           

Thread()

new Thread(new Runnable() {
            public void run() {
                System.out.println(1);
            }
        }, "線程名");
           

守護線程與非守護線程

什麼是守護線程?

和main函數相關。

守護線程和主線程一起銷毀。

如gc線程,垃圾回收機制中的單獨的一個線程,專門用來回收垃圾,主線程結束,守護線程一起結束。

什麼是非守護線程?

與main函數互不影響。

自己建立線程叫做使用者線程,如果主線程停止了,不會影響使用者線程,使用者線程也叫非守護線程。

非守護線程示範代碼:

public static void main(String[] args) {
        new Thread(new Runnable() {
            @lombok.SneakyThrows
            public void run() {
                for (int i = 0; i <= 30; i++) {
                    Thread.sleep(300);
                    System.out.println(i);
                }
            }
        }, "線程名").start();

        System.out.println("主線程執行完畢");
    }
           

執行結果:

閉關修煉(一)多線程線程與程式的差別多線程應用場景繼承方式建立多線程runnable接口建立多線程使用匿名内部類方式建立多線程Callable方式建立多線程線程池方式建立多線程多線程常見API守護線程與非守護線程多線程運作狀态join()方法作用案例多線程分批處理資料

主線程結束和非守護線程沒有依賴關系。

設定守護線程

thread.setDaemon(true);

讓守護線程和主線程一起銷毀。

public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @SneakyThrows
            public void run() {
                for (int i = 1000; i <= 3000; i++) {
                    System.out.println(i);
                }
            }
        }, "線程名");
        thread.setDaemon(true);
        thread.start();
        for (int i = 0; i <500; i++){
            System.out.println(i);
        }
        System.out.println("主線程執行完畢");
    }
           

多線程運作狀态

閉關修煉(一)多線程線程與程式的差別多線程應用場景繼承方式建立多線程runnable接口建立多線程使用匿名内部類方式建立多線程Callable方式建立多線程線程池方式建立多線程多線程常見API守護線程與非守護線程多線程運作狀态join()方法作用案例多線程分批處理資料
  1. 建立狀态

    new Thread

  2. 就緒狀态

    就緒狀态,等待cpu排程

  3. 運作狀态

    擷取cpu排程權

  4. 死亡狀态

    run方法執行完畢,或者stop方法

  5. 休眠(阻塞)狀态

    調用了sleep方法或者wait()方法,運作狀态轉到阻塞狀态,再到就緒狀态

join()方法作用

現有線程A和線程B,線程A調用了線程B的join方法,A等待B線程執行完畢之後再繼續執行。也就是說線程A在調用B的join方法後,釋放了執行權,讓B先執行。

主線程讓子線程先執行,方法:在主函數中調用子線程的join方法,例子如下:

@SneakyThrows
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @SneakyThrows
            public void run() {
                for (int i = 1000; i <= 1500; i++) {
                    System.out.println(i);
                }
                System.out.println("子線程執行完畢!");
            }
        });
        thread1.start();
        thread1.join();
        for (int i = 0; i <= 50; i++) {
            System.out.println(i);
        }
    }
           

面試題順序執行T1,T2,T3

現有T1,T2,T3三個線程,怎麼保證T2在T1執行完後執行,T3在T2執行完後執行?

@SneakyThrows
    public static void main(String[] args) {

        final Thread thread1 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i <= 10; i++) {
                    System.out.println(i);
                }
                System.out.println("子線程1執行完畢!");
            }
        });
        final Thread thread2 = new Thread(new Runnable() {
            @SneakyThrows
            public void run() {
                thread1.join();
                for (int i = 11; i <= 20; i++) {
                    System.out.println(i);
                }
                System.out.println("子線程2執行完畢!");
            }
        });
        Thread thread3 = new Thread(new Runnable() {
            @SneakyThrows
            public void run() {
                thread2.join();
                for (int i = 21; i <= 30; i++) {
                    System.out.println(i);
                }
                System.out.println("子線程3執行完畢!");
            }
        });

        thread1.start();
        thread2.start();
        thread3.start();
       
    }
           

案例多線程分批處理資料

端口掃描

public void threadScanPort() {
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        portCount.set(0);
        int totalThreadNum = 100;
        for (int n = 0; n <= totalThreadNum; n++) {
            executorService.execute(new ScanHandler(n, totalThreadNum));
        }
    }
    
           
class ScanHandler implements Runnable {

        //用于端口掃描的總共線程數
        private int totalThreadNum;
        // 線程号
        private int threadNo;
        
        
        public ScanHandler(int threadNo, int totalThreadNum) {
            this.totalThreadNum = totalThreadNum;
            this.threadNo = threadNo;
        }

        @Override
        public void run() {
            int startPort = Integer.parseInt(startPortField.getText());
            int endPort = Integer.parseInt(endPortField.getText());
            String host = targetIp.getText();
            //startPort和endPort為成員變量,表示需要掃描的起止端口
            for (int port = startPort + threadNo; port <= endPort; port = port + totalThreadNum) {
                try {
                    Socket socket = new Socket();
                    socket.connect(new InetSocketAddress(host, port), 200);
                    socket.close();
                    String msg = "端口 " + port + " is open\n";
                    Platform.runLater(() -> {
                        taDisplay.appendText(msg);
                    });
                } catch (IOException e) {
                    System.out.println(port + "is close");
                }
                portCount.incrementAndGet();//掃描的端口數+1
            }

            if (portCount.get() == (endPort - startPort + 1)) {
                portCount.incrementAndGet();//加1,使得不再輸出下面的線程掃描結束的資訊
                Platform.runLater(() -> {
                    taDisplay.appendText("\n----------------多線程掃描結束--------------------\n");
                });
            }
        }
    }