Java 線程 —— 基礎

前言

當回顧其線程的時候,感覺什麼都忘了,在網上查資料的時候,都沒有什麼好的文章,決定寫一系列關於Java線程的文章。

這篇博客特別基礎,所以推薦一篇寫的比較好文章 —— Java多線程學習(吐血超詳細總結)

線程和進程概述

  • 進程:計算機中特定功能的程序在數據集上的一次運行。
  • 線程:線程是進程的一個單元。
  • 多線程:一個進程中有多個線程在同時運行,如迅雷下載,迅雷軟件的一次運行就是一個進程,那麼在迅雷中可以同時下載多個電影,這就是多線程(每一個下載都是一個線程)
  • Jvm是多線程的,在我們運行jvm的時候後臺會運行垃圾回收的線程,來清理沒有被引用的對象。

舉個例子:

進程:啓動一個LOL.exe就叫一個進程。 接着又啓動一個DOTA.exe,這叫兩個進程。
線程:線程是在進程內部同時做的事情,比如在LOL裏,有很多事情要同時做,比如"蓋倫” 擊殺“提莫”,同時“賞金獵人”又在擊殺“盲僧”,這就是由多線程來實現的。


常用創建線程的兩種方式

一種是繼續Thread類,另外一種是實現Runable接口.(其實準確來講,應該有三種,還有一種是實現Callable接口,並與Future、線程池結合使用,此文這裏不講這個,有興趣看這裏Java併發編程與技術內幕:Callable、Future、FutureTask、CompletionService 、BF360C )

使用Thread創建線程的步驟:

  1. 自定義一個類,繼承java.lang包下的Thread類
  2. 重寫run方法
  3. 將要在線程中執行的代碼編寫在run方法中
  4. 創建上面自定義類的對象
  5. 調用start方法啓動線程
1. 繼承Thread類
//1.自定義一個類,繼承java.lang包下的Thread類
public class MyThreed extends Thread {
    //通過構造函數爲線程起名字
    private String name;
    public MyThreed(String name) {
        this.name = name;
    }

    //2.重寫run方法
    @Override
    public void run() {
    	//3.將要在線程中執行的代碼編寫在run方法中
        for (int i = 0; i <=50 ; i++) {
            System.out.println(name+"跑了"+i+"米");
        }
    }
}

測試

public static void main(String[] args) {
 		//4.創建上面自定義類的對象
        MyThreed myThreed=new MyThreed("懶洋洋");
        myThreed.start();
        MyThreed myThreed1=new MyThreed("灰太狼");
        //5.調用start方法啓動線程
        myThreed1.start();
        System.out.println("方法結束");
    }
2. 實現Runnable接口創建線程

使用Runnable創建線程步驟:

  1. 自定義一個類實現java.lang包下的Runnable接口
  2. 重寫run方法
  3. 將要在線程中執行的代碼編寫在run方法中
  4. 創建自定義類的對象
  5. 創建Thread對象並將上面自定義類的對象作爲參數傳遞給Thread的構造方法
  6. 調用start方法啓動線程
// 1.自定義一個類實現java.lang包下的Runnable接口
public class MyRunable implements Runnable {
    private String name;

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

	 // 2.重寫run方法
    @Override
    public void run() {
    	 // 3.將要在線程中執行的代碼編寫在run方法中
        for (int i = 0; i <=50 ; i++) {
            System.out.println(name+"跑了"+i+"米");
        }
    }


	//測試
	 public static void main(String[] args) {
 		// 4.創建Thread對象並將上面自定義類的對象作爲參數傳遞給Thread的構造方法
        Thread thread = new Thread(new MyRunable("喜洋洋進度"));
        Thread thread1 = new Thread(new MyRunable("灰太狼進度"));
        //6.調用start方法啓動線程
        thread.start();
        thread1.start();
    }

多線程創建的二種方式對比

繼承Thread

  • 優點:可以直接使用Thread類中的方法,代碼簡單
  • 缺點:繼承Thread類之後就不能繼承其他的類

實現Runnable接口

  • 優點:即時自定義類已經有父類了也不受影響,因爲可以實現多個接口
  • 缺點: 在run方法內部需要獲取到當前線程的Thread對象後才能使用Thread中的方法

線程的聲明週期

線程是一個動態執行的過程,它也有一個從產生到銷燬的過程。

下圖顯示了一個線程完整的生命週期。
在這裏插入圖片描述

  1. 新建: 線程被new出來
  2. 準備就緒:線程具有執行的資格,即線程調用了start(),沒有執行的權利
  3. 運行:具備執行的資格和具備執行的權利
  4. 阻塞:沒有執行的資格和執行權利
  5. 銷燬: 線程的對象變成垃圾,釋放資源。

線程的併發

我們舉個例子,讓我們更加懂得併發。

現有一家電影院,現哪吒之魔童降世的電影票共有一百張,共有三個窗口賣票,程序怎麼實現?

在這裏插入圖片描述

public class MoviceThreed extends Thread {
    //爲線程起別名
    private String name;
    //共工100張靜態變量的票
    static int tickets = 100;
    //創建一個鎖對象,這個對象是多個線程對象共享的數據
    static Object obj = new Object();


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

    @Override
    public void run() {
        //賣票是持續的
        while (true) {
            synchronized (obj) {
                if (tickets > 0) {
                    System.out.println(name + "賣出座位是" + (tickets--) + "號");
                } else {
                    break;
                }
            }
        }
        System.out.println(name+"賣票結束");
    }
}

public class MoviceTest {

    public static void main(String[] args) {
        MoviceThreed m1 = new MoviceThreed("窗口1");
        MoviceThreed m2 = new MoviceThreed("窗口2");
        MoviceThreed m3 = new MoviceThreed("窗口3");
        m1.start();
        m2.start();
        m3.start();
    }
}

如果我麼用傳統的方式的話會發生併發行爲,可能1號窗口,2號窗口賣出同一張票,這樣是不對的所以我們上面代碼用到 synchronized

  • 語法:

    synchronized(鎖對象){
    //操作共享資源的代碼
    }
    
同步代碼加在什麼地方?
  1. 代碼被多個線程訪問
  2. 代碼中有共享的數據
  3. 共享數據被多條語句操作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章