初學者的多線程理解

(一)線程與進程

(1)概述

進程:

是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間線程。

線程:

是進程中的一個執行路徑,共享一個內存空間,線程之間可以自由切換併發執行。一個進程最少有一個線程。

實際上是在進程基礎之上的進一步劃分,一個進程啓動之後,裏面的若干執行路徑又可以劃分成若干個線程。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

(2)由來

在早期的操作系統裏,計算機都是單核處理器,只有一個核心,進程是執行程序的最小單位,任務調度採用時間片輪轉的搶佔式方式進行進程調度。每個進程都有各自的一塊獨立的內存,保證進程彼此間的內存地址空間的隔離。

計算機病毒的誕生讓計算機程序的安全得到重視。在早期在只有一個核心的情況下,計算機一旦中病毒就內存有可能被佔滿,cpu不會執行其他的操作,也無法進行鍵盤輸入鼠標移動等操作。

隨着計算機技術的發展,進程出現了很多弊端
(1)進程的創建、撤銷和切換的開銷比較大
(2)由於對稱多處理機的出現,可以滿足多個運行單位,而多進程並行開銷過大。

線程的引入:
由於進程出現了很多弊端,這個時候就引入了線程的概念。 線程也叫輕量級進程,它是一個基本的CPU執行單元,也是程序執行過程中的最小單元,由線程ID、程序計數器、寄存器集合 和堆棧共同組成。線程的引入減小了程序併發執行時的開銷,提高了操作系統的併發性能。 線程沒有自己的系統資源,只擁有在運行時必不可少的資源。但線程可以與同屬與同一進程的其他線程共享進程所擁有的其他資源。

(3)線程的調度

分時調度

所有線程輪流使用 CPU 的使用權,平均分配每個線程佔用 CPU 的時間。

搶佔式調度

優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那麼會隨機選擇一個(線程隨機性),Java使用的爲搶佔式調度。
CPU使用搶佔式調度模式在多個線程間進行着高速的切換。對於CPU的一個核新而言,某個時刻,只能執行一個線程,而 CPU的在多個線程間切換速度相對我們的感覺要快,看上去就是 在同一時刻運行。 其實,多線程程序並不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的 使用率更高

(4)多進程與多線程的理解

在此引用一下廖雪峯老師的網站

(5)Thread類(線程類)

<1>構造方法:

在這裏插入圖片描述

<2>靜態方法

在這裏插入圖片描述

<3>實例方法

在這裏插入圖片描述在這裏插入圖片描述

<3>具體方法

在這裏插入圖片描述在這裏插入圖片描述

<4>常用方法詳解(實例演示)

 public static void main(String[] args) {
   
   
        //輸出當前進程的名字:main
        System.out.println(Thread.currentThread().getName());
        //必須加.start();不然線程無法執行
        new Thread(new MyRunnable(),"端午").start();
        new Thread(new MyRunnable(),"重陽").start();
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
        //由運行結果可以看出線程不保證屬性,是搶佔式
        Thread t = new Thread(new MyRunnable());
        t.setName("除夕");
        t.start();
}
public  static class MyRunnable implements Runnable{
   
   
    @Override
    public void run() {
   
   
        //輸出當前任務進程的名字
        System.out.println(Thread.currentThread().getName());
    }
}

在這裏插入圖片描述

分析: 此程序運用的常用方法有

變量和類型 方法 描述
void run() 如果此線程是使用單獨的Runnable運行對象構造的,則調用該Runnable對象的run方法; 否則,此方法不執行任何操作並返回。
void start() 導致此線程開始執行; Java虛擬機調用此線程的run方法。
static Thread currentThread() 返回對當前正在執行的線程對象的引用。
void setName​(String name) 將此線程的名稱更改爲等於參數 name 。
構造器 Thread​(Runnable target, String name) 分配新的 Thread對象。
構造器 Thread​(Runnable target) 分配新的 Thread對象。

(6)多線程的創建方式

<1>繼承Thread類

public static void main(String[] args) {
   
   
        MyThread myThread = new MyThread();
        //不用調用run,用start
        myThread.start();
        for(int i = 0;i<5;i++) {
   
   
            System.out.println("處處蚊子咬"+i);
        }
}
//繼承Thread類
public class MyThread extends  Thread{
   
   
    @Override
    public void run() {
   
   
      for (int i = 0;i<5;i++)
    System.out.println("春眠不覺曉"+i);
    }
}

多次運行結果不一致
在這裏插入圖片描述
在這裏插入圖片描述
可以看出Java是搶佔式分配
程序運行圖示:
在這裏插入圖片描述
在這裏插入圖片描述





<2>實現Runnable接口

 public static void main(String[] args) {
   
   
        //1.創建一個任務對象
        MyRunnable r = new MyRunnable();
        //2.創建一個線程,並分配一個任務對象
        Thread k = new Thread(r);
        //3.執行線程
        k.start();
        for(int i =0;i<5;i++){
   
   
            System.out.println("處處蚊子咬"+i);
        }
 }
 //實現Runnable接口
public class MyRunnable implements Runnable{
   
   

    @Override
    public void run() {
   
   
        //線程執行的任務
        for(int i =0;i<5;i++){
   
   
            System.out.println("春眠不覺曉"+i);
        }
    }
}

在這裏插入圖片描述
在這裏插入圖片描述

<3>兩種創建方式的區別

實現Runnable 與 繼承Thread 相比有
如下優勢
1.通過創建任務,然後給線程分配的方式來實現的多線程,更適合多個線程同時執行相同任務的情況。
2.可以避免單繼承所帶來的侷限性。
3.任務與線程本身是分離的,提高了程序的健壯性。
4.線程池技術只接受Runnable類型的任務,不接受Thread類型的任務。
然而不代表繼承Thread方法就不能用,下面運用內部類的方法就很方便。





  public static void main(String[] args) {
   
   
        //創建一個匿名內部類,任務只用執行一次不需要調用
        new Thread(){
   
   
            @Override
            public void run() {
   
   
                for(int i =0;i<5;i++){
   
   
                    System.out.println("春眠不覺曉"+i);
                }
            }
        }.start();
        for(int i =0;i<5;i++){
   
   
            System.out.println("處處蚊子咬"+i);
        }
    }

在這裏插入圖片描述

(七)線程的休眠:

public static void main(String[] args) throws InterruptedException {
   
   
      for(int i =0;i<5;i++){
   
   
        System.out.println(i);
        //休眠1000毫秒(1)秒,sleep是靜態方法
        Thread.sleep(1000);
      }
    }

運行結果:
在這裏插入圖片描述

(八)線程的中斷:

  public static void main(String[] args) throws InterruptedException {
   
   
        Thread t = new Thread(new MyRunnable());
        t.start();
        for(int i =0;i<5;i++){
   
   
            System.out.println(Thread.currentThread().getName()+i);
     try{
   
   
        Thread.sleep(1000);
      }catch (InterruptedException e){
   
   
         e.printStackTrace();
     }
        }
        //main線程已經結束,調用t線程中斷,t線程進入異常return跳出線程
        t.interrupt();
    }
 public static class MyRunnable implements Runnable{
   
   

        @Override
        public void run() {
   
   
            for(int i =0;i<10;i++){
   
   
                System.out.println(Thread.currentThread().getName()+i);
                try {
   
   
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
   
   
                  
                    System.out.println("發現中斷標記,線程自殺");
                    return;
                }
            }
        }
 }

運行結果:
在這裏插入圖片描述

(九)線程守護

線程的分類: 守護線程和用戶線程

用戶線程:當一個進程不包含任何的存活的用戶線程時,進程結束。
守護線程:守護用戶線程的,當最後一個用戶線程結束時,所以守護線程自動死亡。

    public static void main(String[] args) throws InterruptedException {
   
   
        Thread t = new Thread(new MyRunnable());
        //設置守護線程
        t.setDaemon(true);
        t.start();
        for(int i =0;i<5;i++){
   
   
            System.out.println(Thread.currentThread().getName()+":"+i);
            try{
   
   
              Thread.sleep(1000);
            }catch (InterruptedException e){
   
   
               e.printStackTrace();
            }
        }
        //main方法還需要結束,守護線程還沒有死,再輸出一次
   }
    public static class MyRunnable implements Runnable{
   
   
        @Override
        public void run() {
   
   
            for(int i =0;i<10;i++){
   
   
             System.out.println(Thread.currentThread().getName()+":"+i);
                try {
   
   
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
   
   
                        e.printStackTrace();
                }
            }
        }
    }

運行結果:
在這裏插入圖片描述

<10>線程安全

【1】線程不安全

 public static void main(String[] args) {
   
   
       Runnable t = new Ticket();
       new Thread(t).start();
       new Thread(t).start();
       new Thread(t).start();
 }
    public static class Ticket implements Runnable{
   
   
        private  int count = 5;
        @Override
        public void run() {
   
   

            while(count>0){
   
   
                System.out.println("正在準備出票");
                try {
   
   
                    Thread.sleep(1000);
                }catch (InterruptedException e){
   
   
                    e.printStackTrace();
                }
                count--;
                System.out.println("出票成功,還剩票數爲:"+count);
            }
        }
    }

運行結果:
在這裏插入圖片描述
可以看出不應該出現負數,三個線程執行同一個任務造成了線程不安全。

【2】線程不安全原因

在判斷和使用數據直接間隔了幾行代碼,在中間執行的時候被其他線程插足,把數據改變了,造成了數據不符合預期。

【3】解決方法

一個線程在間隔區執行時不讓其他線程插足,排隊執行。

{1}解決方法1:同步代碼塊

 public static void main(String[] args) {
   
   
 //只創建了一個new Ticket對象,所以是同一個o,同一把鎖才能排隊
        Runnable t = new Ticket();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
    }

    public static class Ticket implements Runnable {
   
   
        private int count = 5;
        private  Object o = new Object();
        @Override
        public void run() {
   
   
            while (true) {
   
   
            //開始上鎖
                synchronized (o) {
   
   
                    if (count > 0) {
   
   
                        System.out.println("正在準備出票");
                        try {
   
   
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
   
   
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName()+"出票成功,還剩票數爲:" + count);
                    }else{
   
   
                        break;
                    }
                }
            }
        }
    }

運行結果:
在這裏插入圖片描述
線程0第一個搶到鎖,在循環執行結束鎖標誌被清除,此時距離鎖位置最近,反手又打上標記。所以搶到的機率最大,程序效率不高。

{2}解決方法2:同步方法

  public static void main(String[] args) {
   
   
        Runnable t = new Ticket();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
    }
    public static class Ticket implements Runnable {
   
   
        private int count = 10;
        @Override
        public void run() {
   
   
            while (true) {
   
   
            //在循環中調用sale方法
                boolean flag = sale();
                if (!flag) {
   
   
                //退出死循環
                    break;
                }
            }
        }
//給方法上鎖
        public synchronized boolean sale() {
   
   
            if (count > 0) {
   
   
                System.out.println("正在準備出票");
                try {
   
   
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
   
   
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName() + "出票成功,還剩票數爲:" + count);
                return true;
            } else {
   
   
                return false;
            }
        }
    }

運行結果:
在這裏插入圖片描述

{3}解決方法3:顯示鎖LOCK

同步方法塊和同步方法屬於隱示鎖

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo6 {
   
   
    public static void main(String[] args) {
   
   
       Runnable t = new Ticket();
       new Thread(t).start();
       new Thread(t).start();
       new Thread(t).start();
    }

    public static class Ticket implements Runnable {
   
   
        private int count = 5;
         //創建鎖對象
        private Lock l = new ReentrantLock();
        @Override
        public void run() {
   
   
            while (true) {
   
   
            //上鎖
                l.lock();
                if (count > 0) {
   
   
                    System.out.println("正在準備出票");
                    try {
   
   
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
   
   
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println("出票成功,還剩票數爲:" + count);
                } else {
   
   
                    break;
                }//if語句運行結束,解鎖
                l.unlock();
            }
        }
    }
}

運行結果:
在這裏插入圖片描述

(二)同步與異步

同步: 排隊執行 , 效率低但是安全.
異步: 同時執行 , 效率高但是數據不安全.

(三)併發與並行

併發: 指兩個或多個事件在同一個時間段內發生。
並行: 指兩個或多個事件在同一時刻發生(同時發生)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章