03 Java線程學習1

  • 線程就是獨立的執行路徑
  • 在程序運行時,即使沒有自己創建線程,後臺也會有多個線程,如主線程、gc線程
  • main()稱之爲主線程,爲系統的入口,用於執行整個程序
  • 在一個進程中,如果開闢了多個線程,線程的運行由調度器安排調度,調度器是與操作系統密切相關的,先後順序是不能人爲干預的
  • 對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制
  • 線程會帶來額外的開銷,如CPU調度時間,併發控制開銷
  • 每個線程在自己的工作內存交互,內存控制不當會造成數據不一致

介紹

Java 給多線程編程提供了內置的支持。 一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。

一個進程包括由操作系統分配的內存空間,包含一個或多個線程。一個線程不能獨立的存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守護線程都結束運行後才能結束。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6Wf0bDCR-1593089275811)(C:\Users\Tangs\AppData\Roaming\Typora\typora-user-images\1593048108216.png)]

Process與Thread

程序是指令和數據的有序集合,其本身沒有任何運行的含義,是一個靜態的概念。

進程則是執行程序的一次執行過程,他是一個動態的概念。是系統資源分配的單位。

通常一個進程中可以包含若干個線程。線程是CPU調度和執行的基本單位。

**注意:**很多多線程是模擬出來的,真正的多線程指有多個CPU,即多核,如服務器。如果是模擬出來的多線程,即在一個CPU的情況下,在同一時間點,CPU只能執行一個代碼,因爲切換的很快,所以認爲是同時執行。

線程創建

  • Thread class 繼承Thread類
  • Runnable接口 實現Runnable接口
  • Callable接口 實現Callable接口

Thread類

  • 自定義線程類繼承Thread類
  • 重寫**run()**方法,編寫線程執行體
  • 創建線程對象,調用**start()**方法啓動線程
public class TestThread extends Thread{
    @Override
    public void run(){
        //run方法線程體
        for(int i = 0; i < 20; i++){
            System.out.println("我在看代碼---"+i)}
    }
    
    public static void main(String[] args){
        //main線程,主線程
        //創建一個線程對象
        TestThread testThread = new TestThread();
        
        //調用start()方法開啓線程
        testThread.start();
        
        for(int i = 0; i < 20; i++){
            System.out.println("多線程執行"+i);
        }
    }
}

實例:線程同時下載圖片

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

public class ThreadTest extends Thread {
    private String url; //圖片下載路徑
    private String name;    //圖片名字

    public ThreadTest(String url, String name) {
        this.url = url;
        this.name = name;
    }

    //下載圖片線程執行體
    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url, name);
        System.out.println("下載的文件名爲:"+name);
    }

    public static void main(String[] args) {
        ThreadTest t1 = new ThreadTest("https://img-blog.csdn.net/20161120091317312", "1.jpg");
        ThreadTest t2 = new ThreadTest("https://img-blog.csdn.net/20161120091128267", "2.jpg");
        ThreadTest t3 = new ThreadTest("https://img-blog.csdn.net/20161120091439735", "3.jpg");

        t1.start();
        t2.start();
        t3.start();
    }
}

//下載器
class WebDownloader{
    //    下載方法
    public void downloader(String url, String name){
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Runnable 接口

  • 創建Runnable接口的實現類對象
  • 創建線程對象,通過線程對象來開啓我們的線程(代理模式
public class TestThread implements Runnable{
    @Override
    public void run(){
        //run方法線程體
        for(int i = 0; i < 20; i++){
            System.out.println("我在看代碼---"+i)}
    }
    
    public static void main(String[] args){
        //main線程,主線程
        //創建runnable接口的實現類對象
        TestThread testThread = new TestThread();
        
        //創建線程對象,通過線程對象來開啓我們的線程(代理)
        new Thread(testThread).start();
        
        for(int i = 0; i < 20; i++){
            System.out.println("多線程執行"+i);
        }
    }
}

繼承Thread類與實現Runnable接口對比

繼承Thread類

  • 子類繼承Thread類具備多線程能力
  • 啓動線程:子類對象.start();
  • 不建議使用:避免OOP單繼承侷限性

實現Runnable接口

  • 實現接口Runnable具有多線程能力
  • 啓動線程:傳入目標對象+Thread對象.start();
  • 推薦使用:避免單繼承的侷限性,方便靈活,方便同一對象被多個線程使用

注意:當同一資源被多個線程利用時,會出現錯誤。例如以下購買車票程序,多人購買到同一張車票

public class ThreadTest1 implements Runnable {

    //票數
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true){
            if(ticketNums <= 0){
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"獲得了第"+ticketNums--+"張票");
        }
    }

    public static void main(String[] args) {
        ThreadTest1 threadTest1 = new ThreadTest1();

        new Thread(threadTest1, "小米").start();
        new Thread(threadTest1, "老師").start();
        new Thread(threadTest1, "黃牛").start();
    }
}

運行結果:

老師獲得了第10張票
小米獲得了第8張票
黃牛獲得了第9張票
小米獲得了第7張票
老師獲得了第6張票
黃牛獲得了第6張票
小米獲得了第5張票
老師獲得了第5張票
黃牛獲得了第4張票
黃牛獲得了第3張票
小米獲得了第2張票
老師獲得了第3張票
黃牛獲得了第1張票
小米獲得了第0張票
老師獲得了第0張票

案例:龜兔賽跑

背景介紹:

  1. 首先來個賽道距離,然後距離終點越來越近
  2. 判斷比賽是否結束
  3. 打印出勝利者
  4. 龜兔賽跑開始
  5. 故事中是烏龜贏了,兔子需要睡覺,所以我們通過sleep模擬兔子睡覺
  6. finally,烏龜贏得比賽

程序代碼:

public class ThreadRace implements Runnable {
    private static String winner;
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            //模擬兔子睡覺
            if (Thread.currentThread().getName().equals("兔子") && i%10==0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判斷比賽是否結束
            boolean flag = gameOver(i);
            //比賽有winner,退出跑步
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
        }
    }

    //判斷比賽是否完成
    private boolean gameOver(int steps){
        if (winner != null) {
            return true;
        }else{
            if (steps >= 100){
                winner = Thread.currentThread().getName();
                System.out.println("winner is "+ winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        ThreadRace threadRace = new ThreadRace();
        new Thread(threadRace, "兔子").start();
        new Thread(threadRace, "烏龜").start();

    }
}

Callable接口

  1. 實現Callable接口,需要返回值類型
  2. 重寫call方法,需要拋出異常
  3. 創建目標對象
  4. 創建執行服務:ExecutorService service = Executors.newFixedThreadPool(3);
  5. 提交執行:Future<Boolean> r1 = service.submit(t1);
  6. 獲取結果:boolean rs1 = r1.get();
  7. 關閉服務:service.shutdown();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章