java併發(一)進程和線程

一,進程和線程

1.1 進程和線程

1.1.1 進程

  • 程序由指令和數據組成,但這些指令要運行,數據要讀寫,都必須將指令加載到CPU,數據加載到內存,在指令運行過程中還需要用到磁盤,網絡等設備,進程就是用來加載指令,管理內存,和管理IO的
  • 當一個程序運行,程序代碼從磁盤加載到內存就相當於開啓了一個進程
  • 進程相當於一個程序的實例

1.1.2 線程

  • 一個進程中有一到多個線程
  • 一個線程就是一個指令流,將指令流中的一條條指令以一定順序交給CPU執行
  • java中,線程是最小調度單位,進程是資源分配的最小單位

1.1.3 區別

  • 進程擁有共享的資源,如內存空間,供其內部的線程共享
  • 進程間的通信較爲複雜
    • 同一臺計算機的進程通信稱爲IPC
    • 不同計算機之前的進程通過網絡通信
  • 線程通信較爲簡單,因爲他們共享進程內的內存
  • 線程更加輕量,上下文切換成本更低

1.2 並行和併發

單核 cpu 下,線程實際還是 串行執行 的。操作系統中有一個組件叫做任務調度器,將 cpu 的時間片(windows 下時間片小約爲 15 毫秒)分給不同的程序使用,只是由於 cpu 在線程間(時間片很短)的切換非常快,人類感 覺是 同時運行的 。總結爲一句話就是: 微觀串行,宏觀並行

一般會將這種 線程輪流使用 CPU 的做法稱爲併發, concurrent

在這裏插入圖片描述

多核 cpu下,每個 核(core)都可以調度運行線程,這時候線程可以是並行的。

在這裏插入圖片描述

1.3 java中的線程

現代操作系統在運行一個程序時,會爲其創建一個進程,例如,啓動一個java程序,操作系統會創建一個java進程

現代操作系統調度的最小單位是線程,也叫輕量級進程,在一個進程裏可以創建多個線程,這些線程都擁有各自的計數器,堆棧和局部變量等屬性,並且能訪問共享的內存變量,處理器在這些線程上高速切換

1.3.1 創建線程

1.繼承Thread,重寫run方法

    public static class ThreadClass extends Thread{
        @Override
        public void run() {
            System.out.println("繼承Thread,重寫run方法");
        }
    }

啓動線程

        Thread t5 = new ThreadClass();
        t5.setName("t5");
        t5.start();

2.實現runnable接口,重寫run方法

    public static class RunClass implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"  分離runnable");
        }
    }

啓動線程

        Thread t2 = new Thread(new RunClass());
        t2.setName("t2");
        t2.start();

3.實現callable接口,重寫call方法,可以獲取到返回值

    private static int i = 1;
    public static class CallClass implements Callable<Integer>{
        @Override
        public Integer call() throws Exception {
            for(int a = 0;a < 100000;a++)
                i++;
            return i++;
        }
    }

啓動線程

        FutureTask<Integer> futureTask = new FutureTask<>(new CallClass());
        Thread t4 = new Thread(futureTask);
        t4.setName("t4");
        t4.start();
        //獲取返回值
        int i = futureTask.get();

我們都知道 JVM 中由堆、棧、方法區所組成,其中棧內存是給誰用的呢?其實就是線程,每個線程啓動後,虛擬 機就會爲其分配一塊棧內存。

  • 每個棧由多個棧幀(Frame)組成,對應着每次方法調用時所佔用的內存
  • 每個線程只能有一個活動棧幀,對應着當前正在執行的那個方法

線程上下文切換

因爲以下一些原因導致cpu不再執行當前的線程,轉而執行另一個線程的代碼:

  • 線程的CPU時間片用完
  • 線程被垃圾回收
  • 有更高優先級的線程要運行
  • 線程自己調用了sleep、yield、wait、join、park、synchronized、lock 等方法

觀察棧楨

    public static void main(String[] args) {
        new Thread(()->{
            method1(10);
        },"t1").start();
        new Thread(()->{
            method1(100);
        },"t2").start();
    }

    public static void method1(int a){
        method2(a++);
    }

    public static void method2(int b){
        method3(b++);
    }

    public static void method3(int c){
        int a = c++;
    }

斷點注意選擇Thread

在這裏插入圖片描述

在這裏插入圖片描述

當一個方法執行完退出了,該方法對應的棧楨就會出棧,相應的內存也會被回收

對於線程t2,執行的方法和線程t1互不影響

在這裏插入圖片描述

當前所有線程

在這裏插入圖片描述

1.3.2 啓動線程start()和run()

  • 直接調用run()並沒有開啓新的線程,只是在當前線程上調用run方法而已
  • 使用start方法是啓動新的線程,通過新的線程執行run方法
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("當前線程:"+Thread.currentThread().getName());
            }
        },"t1");
        t1.run();
    }

在這裏插入圖片描述

一個新構造的線程對象是由其parent線程來進行空間分配

一個線程必須由另外的一個線程來創建完成。對於其他所有的線程最終都是由main線程來創建的。而此時調用currentThread方法的時候獲取到的就是main線程。

1.3.3 sleep()和yeild方法

sleep():

  • 調用 sleep 會讓當前線程從 Running 進入 Timed Waiting 狀態(阻塞)
  • 其它線程可以使用 interrupt 方法打斷正在睡眠的線程,這時 sleep 方法會拋出 InterruptedException
  • 睡眠結束後的線程未必會立刻得到執行
  • 建議用 TimeUnit 的 sleep 代替 Thread 的 sleep 來獲得更好的可讀

yeild():

  • 讓出CPU,從Running轉爲runnable讓其他線程執行

1.3.4 join()方法

join():

  • 如果線程A執行了thread.join(),作用就是,線程A等待線程thread終止之後才從thread.join()返回

join(long time)

  • 還提供了超時等待,如果超過指定時間,就會直接返回,不等線程結束
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1執行1");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1執行2");
        },"t1");
        t1.start();

        System.out.println("主線程執行");
        t1.join();
        System.out.println("等待結束,主線程繼續執行");
    }

在這裏插入圖片描述

join()一般用於一個線程需要等待另一個線程的返回結果才能繼續運行下去的情況,所以一定要得到另一個線程終止爲止,也就是適用於同步的情況

同步,異步:

  • 需要等待結果返回,才能繼續運行就是同步
  • 不需要等待結果返回,就能繼續運行就是異步

1.3.5 interrupt()方法

interrupt():

  • 打斷 sleep,wait,join的線程,這幾個方法都會讓線程進入阻塞狀態

中斷sleep():

被中斷的線程會拋出中斷異常,從而從sleep中甦醒,這也是爲什麼sleep會捕獲中斷異常的原因,而且打斷 sleep 的線程,會清空打斷狀態,即isInterrupted()返回false

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1執行1");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1執行2");
        },"t1");
        t1.start();

        System.out.println("主線程執行");
        Thread.sleep(1000);
        t1.interrupt();
        System.out.println("主線程中斷t1");
    }

在這裏插入圖片描述

打斷正常運行的線程不會清除打斷狀態

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1執行1");
            while(true){

            }
        },"t1");
        t1.start();

        System.out.println("主線程執行");
        Thread.sleep(1000);
        t1.interrupt();
        System.out.println("主線程中斷t1");
        System.out.println("t1中斷狀態字段:"+t1.isInterrupted());
    }

在這裏插入圖片描述

中斷join():

中斷join也會清除打斷狀態

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1執行1");
            while(true){

            }
        },"t1");
        t1.start();

        Thread t2 = new Thread(()->{
            System.out.println("t2等待t1執行完");
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("t2被中斷了");
            }
        },"t2");
        t2.start();

        System.out.println("主線程中斷t2");
        Thread.sleep(1000);
        t2.interrupt();
        System.out.println("t2中斷狀態字段:"+t2.isInterrupted());
    }

在這裏插入圖片描述

中斷判斷

isInterrupted()不會清除打印標記, Thread.interrupted()靜態方法,會清除打印標記

    public static void test() throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("before");
            LockSupport.park();
            System.out.println("after");
            //System.out.println("打斷狀態:" + Thread.currentThread().isInterrupted());
            System.out.println("打斷狀態:" + Thread.interrupted());
            System.out.println("interrupted after");
        });
        t1.start();

        Thread.sleep(3000);

        t1.interrupt();
        t1.join();
        System.out.println("打斷狀態:" + Thread.currentThread().isInterrupted());
    }

1.3.6 守護線程

其他非守護線程終止了,守護線程即使沒終止,也會終止

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1是守護線程");
            while(true){

            }
        },"t1");
        t1.setDaemon(true);
        t1.start();

        Thread t2 = new Thread(()->{
            System.out.println("t2執行");
        },"t2");
        t2.start();
        System.out.println("主線程執行");
    }

1.3.7 線程狀態

五種狀態模型

v

六種狀態模型

在這裏插入圖片描述

1.3.8 wait/notify

monitor中的owner線程發現條件不滿足時調用wait方法,進入waitSet等待,線程狀態變爲WAITING,BLOCKED和WAITING狀態的線程都處於阻塞狀態,不佔用CPU的時間片,BLOCKED線程會在owner線程釋放時喚醒,WAITING線程則在owner線程調用notify和notifyAll時喚醒,喚醒後重新進入entrylist競爭獲取鎖

使用模板:

//        //使用模板
//        synchronized (lock) {
//            while (條件) {
//                //條件不成立
//                lock.wait();
//            }
//            //條件成立,繼續邏輯
//        }
//        synchronized (lock){
//            //喚醒所有線程
//            lock.notifyAll();
//        }

wait和sleep的區別:

  • sleep是Thread的靜態方法,sleep是object的實例方法
  • sleep無需與synchronized配合使用,wait需要獲取鎖配合synchronized配合使用
  • sleep睡眠時不釋放鎖,wait會釋放鎖
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章