Java項目實戰(zhàn)開發(fā)Day19 2020-04-20

內(nèi)容

1.線程與進(jìn)程
2.線程介紹與使用

關(guān)于線程內(nèi)容也可參看我的另外幾篇博客

http://www.lxweimin.com/p/cb7e5dbe7968
http://www.lxweimin.com/p/88d5e3bc726c
http://www.lxweimin.com/p/2783773aa0d8
http://www.lxweimin.com/p/cbc22e2f3be1
http://www.lxweimin.com/p/84756fb84571

一.線程與進(jìn)程

1.什么是進(jìn)程

進(jìn)程,就是正在運行/執(zhí)行的一個程序。進(jìn)程是用于管理其所有的資源的,不進(jìn)行實際的任務(wù)

2.什么是線程

剛才說了,進(jìn)程不完成具體的任務(wù),那么具體任務(wù)由誰來完成呢?沒錯,就是線程!
線程就是完成具體任務(wù)的一個進(jìn)程可以有多個線程。

3.實例說明

比如打開QQ,就是打開一個進(jìn)程。但是我可以同時進(jìn)行聊天、視頻通話和刷QQ空間這三個任務(wù),每一個任務(wù)都由一個特定的線程進(jìn)行
在Java中,我們寫的程序運行起來就是一個進(jìn)程

二.線程介紹與使用

1.主線程

主線程:就類似主路main方法里面的代碼是在主線程里面執(zhí)行的
還有手機什么都不動的時候,會展現(xiàn)出一個主界面,這個展現(xiàn)主界面的就是一個主線程。也就是說
①在Java里面:main方法里面的代碼,就在主線程中跑
②Android/iOS是 :啟動程序看到的UI界面就是UI主線程

2.子線程

子線程:除了主線程之外的都是子線程

3.多線程的作用

在主線程里面,任務(wù)的執(zhí)行順序是從上至下的。如果其中一個任務(wù)需要花費大量時間(比如下載一個很大的數(shù)據(jù)),那么這個任務(wù)后面的任務(wù)就會被阻塞(就類似堵車了),必須等這個任務(wù)結(jié)束才能被執(zhí)行。用戶的體驗效果不好。這個時候就需要將耗時的任務(wù)放在另外一個不在主線程里面執(zhí)行的路徑(類似走小路,這就是子線程)

4.注意點

①主程序就是用來分配/調(diào)配的。
②不管是主線程還是子線程,它都有自己獨立的內(nèi)存空間,執(zhí)行路徑,生命周期
Thread是管理線程的一個類,它繼承Runnable接口
Thread.currentThread()可以獲取當(dāng)前線程的信息,這個方法也是很常用的

5.如何開啟一個線程

1.寫一個類繼承Thread

①創(chuàng)建類繼承Thread
重寫父類的run方法,把具體執(zhí)行的任務(wù)放在run方法里面
(其實run方法也可以通過對象.run()調(diào)用,但是如果這樣調(diào)用的話,程序還是在主線程里面執(zhí)行。這樣就沒啥意義了。所以不要這樣做。start調(diào)用的話,系統(tǒng)會自動將這個任務(wù)放在隊列中,等待調(diào)度,也就是等待操作系統(tǒng)調(diào)用。所以還是要用start方法去使用線程)
②創(chuàng)建類的對象
③調(diào)用start方法開始執(zhí)行

2.寫一個類實現(xiàn)Runnable接口

①創(chuàng)建一個類實現(xiàn)Runnable接口(這個類只是一個任務(wù),并不能直接開啟線程
②創(chuàng)建任務(wù)類(也就是實現(xiàn)Runnable接口的那個類)的對象
③創(chuàng)建Thread類的對象(Thread類可以創(chuàng)建線程,我們只需要將自己的任務(wù)和Thread對象關(guān)聯(lián)起來
④調(diào)用start啟動線程

6.繼承Thread來使用線程

注意使用如何使用線程的構(gòu)造方法以及getName方法的使用

import java.io.*;
import java.util.*;

public class 測試程序{                                                                                                             
                   
            
        public static void main(String[] args){
            System.out.println("main方法"+Thread.currentThread());
            //2.創(chuàng)建具體的對象
            TestThread testThread = new TestThread("子線程");
            
            //3.啟動線程
            testThread.start();
        }

}
//1.創(chuàng)建類繼承Thread
class TestThread extends Thread{
    public TestThread(String s){
        super(s);
    }
    //執(zhí)行的任務(wù)在run方法里
    public void run() {
        //下面是線程需要執(zhí)行的任務(wù)
        System.out.println("TestThread"+getName());
        for(int i = 0;i < 100;i++) {
            System.out.println(i+1);
        }
    }
}
    



輸出

main方法Thread[main,5,main]
TestThread子線程
1
2
(后面的省略)

4.注意點(續(xù))

線程是通過搶占時間片來得到運行機會的,誰搶到了時間片,誰就可以運行。這個時間片是由操作系統(tǒng)來分配的,所以每一次執(zhí)行結(jié)果可能都是不一致的

如下面這段程序

import java.io.*;
import java.util.*;

public class 測試程序{                                                                                                             
                   
            
        public static void main(String[] args){
            System.out.println("main方法"+Thread.currentThread());
            
            //2.創(chuàng)建具體的對象
            TestThread testThread = new TestThread("子線程1");
            TestThread testThread2 = new TestThread("子線程2");
            
            //3.啟動線程
            testThread.start();
            testThread2.start();
        }

}
//1.創(chuàng)建類繼承Thread
class TestThread extends Thread{
    public TestThread(String s){
        super(s);
    }
    //執(zhí)行的任務(wù)在run方法里
    public void run() {
        //下面是線程需要執(zhí)行的任務(wù)
        for(int i = 0;i < 10;i++) {
            System.out.println(getName()+":"+(i+1));
        }
    }
}
    



的結(jié)果是

main方法Thread[main,5,main]
子線程1:1
子線程2:1
子線程2:2
子線程1:2
子線程1:3
子線程1:4
子線程1:5
子線程1:6
子線程2:3
子線程2:4
子線程2:5
子線程2:6
子線程2:7
子線程2:8
子線程2:9
子線程1:7
子線程2:10
子線程1:8
子線程1:9
子線程1:10

但是下一次的結(jié)果就不一定了
也就是說,當(dāng)調(diào)用start方法時,這個線程會自動扔到操作系統(tǒng)的任務(wù)隊列中(線程池),至于這個任務(wù)什么時候被執(zhí)行,我們無法確定,這一點由操作系統(tǒng)來決定。

7.實現(xiàn)Runnable接口來使用線程

注意如何將任務(wù)與Thread聯(lián)系起來

import java.io.*;
import java.util.*;

public class 測試程序{                                                                                                             
                   
            
        public static void main(String[] args){
            System.out.println("main方法"+Thread.currentThread());
            
            //2.創(chuàng)建具體對象
            //這個其實是具體執(zhí)行的任務(wù),這個類不能直接開啟線程,必須依賴于Thread類
            TestRunnable testRunnable = new TestRunnable();
            
            //3.創(chuàng)建一個Thread對象(因為只是實現(xiàn)Runnable,沒有start方法)
            //讓這個線程去執(zhí)行testRunnable的任務(wù),也就是把它與testRunnable關(guān)聯(lián)起來。這個線程名字為:子線程1
            Thread thread = new Thread(testRunnable,"子線程1");
            Thread thread2 = new Thread(testRunnable,"子線程2");
            //不同的線程執(zhí)行相同的任務(wù),其實可以理解成不同的人開同樣的車
            
            //4.啟動線程
            thread.start();
            thread2.start();
        }

}
//1.創(chuàng)建一個類實現(xiàn)Runnable接口
class TestRunnable implements Runnable{

    public void run() {
        //這個線程需要執(zhí)行的任務(wù)
        for(int i = 0;i < 10;i++) {
            System.out.println(Thread.currentThread().getName()+":"+(i+1));
        }
    }
    
}

輸出結(jié)果

main方法Thread[main,5,main]
子線程1:1
子線程1:2
子線程1:3
子線程1:4
子線程1:5
子線程1:6
子線程1:7
子線程1:8
子線程1:9
子線程1:10

8.兩種方式比較

Java不能多繼承,但是可以實現(xiàn)多個接口
所以用實現(xiàn)Runnable接口的那個方式更靈活一點,更容易擴展。但是寫起來稍微麻煩一點(其實也不是很麻煩,就比第一個方法多了一步):也就是說使用實現(xiàn)Runnable接口的方式使用線程更好。
面向接口編程,能夠松耦合。而第一種方式就不夠靈活。

9.線程的生命周期

線程的生命周期

①new一個線程,就可以使線程處于創(chuàng)建狀態(tài)
②使用start,線程就屬于就緒狀態(tài)。當(dāng)搶占到時間片的時候,線程就屬于運行狀態(tài),當(dāng)失去時間片的時候,就再處于就緒狀態(tài)。以此往返。
從就緒狀態(tài)到運行狀態(tài)是由操作系統(tǒng)來實現(xiàn)的,外部無法干預(yù)。
③當(dāng)run方法結(jié)束的時候,線程就處于死亡狀態(tài)了,這是正常結(jié)束。另外,手動讓線程暫停,比如調(diào)用該線程的stop方法(不建議使用stop,因為該方法容易導(dǎo)致死鎖),再或者線程拋出一個未捕獲的Exception或Error的時候,線程也會處于死亡狀態(tài)。
④當(dāng)線程處于運行狀態(tài)時,也有可能處于阻塞狀態(tài)
(1)同步阻塞:使用synchronized就可以使線程處于同步阻塞狀態(tài),當(dāng)鎖解開的時候,就可以再返回就緒狀態(tài)
(2)等待阻塞:使用wait()就可以使線程處于等待狀態(tài),等調(diào)用notify時,就可再返回就緒狀態(tài)
(3)其他阻塞:使用sleep(),join()也可以使線程處于阻塞狀態(tài),等sleep休眠時間到,或者join()線程執(zhí)行完畢,或者io流阻塞結(jié)束。就再返回就緒狀態(tài)

源碼關(guān)于線程狀態(tài)的描述
這里我借用翻譯工具翻譯如下

10.線程常用方法

(1)如何讓一個線程結(jié)束

①不要直接調(diào)用stop方法來結(jié)束一個線程
②好的方法應(yīng)該是:自己寫一個變量/標(biāo)識符,用來標(biāo)識線程結(jié)束的臨界點

比如

class TestThread extends Thread{
    private boolean shouldStop = true;
    public TestThread(String s){
        super(s);
    }
    
    //執(zhí)行的任務(wù)在run方法里
    public void run() {
        //下面是線程需要執(zhí)行的任務(wù)
        while(shouldStop) {
            System.out.println("子線程");
        }
    }
    
    public void terminated() {
        shouldStop = false;
    }
}
(2)線程禮讓和“插隊“

yield():線程禮讓
禮讓的線程會直接進(jìn)入就緒狀態(tài),被禮讓的線程并不一定會一直執(zhí)行。如果禮讓的線程再次獲得時間片,則還會再次執(zhí)行。所以,禮讓是可能失敗的

使用示例

import java.io.*;
import java.util.*;

public class 測試程序{                                                                                                             
                   
            
        public static void main(String[] args){
          TestRunnable runnable = new TestRunnable();
          
          Thread t1 = new Thread(runnable,"奔馳");
                  
          t1.start();
          
          for(int i = 0;i < 200;i++) {
              System.out.println("主線程"+(i+1));
              if(i == 20) {
                  Thread.yield();//禮讓
              }
          }
        }

}
//1.創(chuàng)建一個類實現(xiàn)Runnable接口
class TestRunnable implements Runnable{

    public void run() {
        //這個線程需要執(zhí)行的任務(wù)
        for(int i = 0;i < 200;i++) {
            System.out.println(Thread.currentThread().getName()+":"+(i+1));
        }
    }
    
}

join():插隊
可以使當(dāng)前線程阻塞,插隊的線程執(zhí)行。這個基本是成功的。

使用示例

import java.io.*;
import java.util.*;

public class 測試程序{                                                                                                             
                   
            
        public static void main(String[] args){
          TestRunnable runnable = new TestRunnable();
          
          Thread t1 = new Thread(runnable,"奔馳");
                  
          t1.start();
          
          for(int i = 0;i < 200;i++) {
              if(i == 20) {
                  try {
                    t1.join();
                } catch (InterruptedException e) {
                    // TODO 自動生成的 catch 塊
                    e.printStackTrace();
                }//插隊
              }
              System.out.println("主線程"+(i+1));
          }
        }

}
//1.創(chuàng)建一個類實現(xiàn)Runnable接口
class TestRunnable implements Runnable{

    public void run() {
        //這個線程需要執(zhí)行的任務(wù)
        for(int i = 0;i < 200;i++) {
            System.out.println(Thread.currentThread().getName()+":"+(i+1));
        }
    }
    
}

11.多線程的優(yōu)點和缺點

目前來說:
(1)優(yōu)點:提高應(yīng)用程序的使用率
(2)缺點:如果多個線程操作同一個資源,有可能出現(xiàn)不安全。所以需要解決這種問題。

12.保證線程安全的兩種方式

①Lock 鎖

必須使用的是同一個鎖

class TestRunnable implements Runnable{
    private static Lock lock = new ReentrantLock();
    public void run() {
        //這個線程需要執(zhí)行的任務(wù)
        for(int i = 0;i < 200;i++) {
            lock.lock();//加鎖
            
            lock.unlock();//解鎖
        }
    }
    
}

但是這種方式使用起來比較麻煩

②線程同步

必須保證鎖的是同一個對象。 synchronized可以鎖代碼塊和方法

(1)第一種,隨便弄個Object對象就可以實現(xiàn)同步

class TestRunnable implements Runnable{
    private static Object obj = new Object();
    public void run() {
        //這個線程需要執(zhí)行的任務(wù)
        for(int i = 0;i < 200;i++) {
            synchronized(obj) {
                //這里面放被鎖住的代碼
            }
        }
    }
    
}

(2)但是一般不這樣搞。一般都是鎖this

class TestRunnable implements Runnable{
    private static Object obj = new Object();
    public void run() {
        //這個線程需要執(zhí)行的任務(wù)
        for(int i = 0;i < 200;i++) {
            synchronized(this) {
                
            }
        }
    }   
}

(3)鎖方法

class TestRunnable implements Runnable{
    private static Object obj = new Object();
    public void run() {
        //這個線程需要執(zhí)行的任務(wù)
        for(int i = 0;i < 200;i++) {
            test();
        }
    }
    
    private synchronized void test() {
        
    }
    
}

(4)注意點

不管是鎖代碼塊還是方法,都應(yīng)盡量讓鎖的范圍變小

13.線程間通信

(1)線程與線程之間有時是需要通信、交互的。一般用到以下幾個方法

①wait()讓線程等待
②notify()喚醒某個線程
③notifyAll()喚醒多個線程

(2)注意:

①這三個方法不在Thread里面,而是在Object里面
②這三個方法必須由同步監(jiān)視器來調(diào)用。(總之就是大家都在搶同一個資源)。

(3)使用示例

①要求:使用線程間通信,完成輸出1 a 2 b。。。。。26 z的操作
②掌握使用匿名內(nèi)部類創(chuàng)建線程的方法
③當(dāng)鎖代碼塊的時候,一般鎖住Object對象,從而進(jìn)行線程間通信。
④當(dāng)鎖方法的時候,是哪個對象調(diào)用這個方法就鎖哪個對象
⑤留意:Character.toChars()這個方法
這個方法在JDK幫助文檔里是這樣說的。

JDK幫助文檔

我去查了一下UTF-16,看不懂,但是我使用起來

這三個輸出結(jié)果都是 a。所以目前來說,先不必計較。或者死記住:輸出字符的時候,使用這個方法。(雖然我現(xiàn)在還沒有查出,測試出不使用這個方法有什么危害)

import java.io.*;
import java.util.*;

public class 測試程序{                                                                                                             
                   
        public static int state = 1;//1表示輸出數(shù)字,2表示輸出字母    
        public static void main(String[] args){
/*           TestRunnable t = new TestRunnable();
             Thread task = new Thread(t);
             
             task.start();*/
            //可以使用匿名內(nèi)部類
/*          Thread task = new Thread(new Runnable() {

                public void run() {
                    for(int i = 0;i < 20;i++) {
                        System.out.println(Thread.currentThread().getName()+":"+(i+1));
                    }
                }
                
            });*/
            
            
            Object obj = new Object();
            new Thread(new Runnable() {
                int num = 1;
                public void run(){
                    //輸出數(shù)字
                    while(true) {
                        synchronized(obj) {//爭奪obj資源
                            //判斷當(dāng)前是不是在輸出字母
                            if(state != 1) {
                                //當(dāng)前線程需要等待一下
                                try {
                                    obj.wait();
                                } catch (InterruptedException e) {
                                    // TODO 自動生成的 catch 塊
                                    e.printStackTrace();
                                }
                            }
                            
                            //輸出數(shù)字
                            System.out.println(num);
                            num++;
                            if(num > 26) {
                                break;
                            }
                        
                            //喚醒當(dāng)前obj鎖上的其他等待的線程
                            state = 2;
                            obj.notify();   
                        }                   
                    }
                }
                
            }).start();
            
            new Thread(new Runnable() {
                char alpha = 'a';
                public void run() {
                    //輸出字母
                    while(true) {
                        synchronized(obj) {//爭奪obj資源
                            //判斷當(dāng)前是不是在輸出數(shù)字
                            if(state != 2) {
                                //當(dāng)前線程需要等待一下
                                try {
                                    obj.wait();
                                } catch (InterruptedException e) {
                                    // TODO 自動生成的 catch 塊
                                    e.printStackTrace();
                                }
                             }
                            
                            //System.out.println("程序執(zhí)行到這里了");
                            //輸出字母
                            System.out.println(Character.toChars(alpha));//Character.toChars(alpha)
                            alpha++;
                            if(alpha > 'z') {
                                break;
                            }
                            
                            state = 1;
                            obj.notify();
                            }
                        }

                    }
                
                
            }).start();
            
        
        }

}

上面是鎖的代碼塊,可以看出上面的代碼不太簡潔。下面使用鎖方法的方式來完成同樣的功能。
要記住:使用同一對象來調(diào)用方法。

import java.io.*;
import java.util.*;

public class 測試程序{                                                                                                             
        static Data d = new Data();           
        public static void main(String[] args){
               new Thread(new Runnable() {

                public void run() {
                    d.printNum();                   
                }                  
               }).start();
               
               new Thread(new Runnable() {

                public void run() {
                    d.printAlpha();
                }                  
                }).start();
        
        }
     

}
class Data{
    int num = 1;
    int alpha = 'a';
    int state = 1;
    
    //此時鎖的是當(dāng)前類的對象,哪個對象調(diào)用這個方法就鎖哪個對象
    public synchronized void printNum() {
        while(true) {
            if(state != 1) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            System.out.println(num);
            num++;
            if(num > 26) {
                break;
            }
            state = 2;
            this.notify();
        }
    }
    
    public synchronized void printAlpha(){
        while(true) {
            if(state != 2) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            System.out.println(Character.toChars(alpha));
            alpha++;//加的是ASCⅡ碼
            if(alpha > 'z') {//注意,這里是'z',而不是26
                break;
            }
            state = 1;
            this.notify();
        }
    }
}



總結(jié)

還是同樣的感受:相比第一次學(xué),這一次學(xué)的更多,更透徹了。也解決了很多問題。下一步就是多寫代碼,不斷進(jìn)行鞏固!加油!!要學(xué)網(wǎng)絡(luò)編程了!!激動!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,482評論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,986評論 1 18
  • 林炳文Evankaka原創(chuàng)作品。轉(zhuǎn)載自http://blog.csdn.net/evankaka 本文主要講了ja...
    ccq_inori閱讀 668評論 0 4
  • 一、認(rèn)識多任務(wù)、多進(jìn)程、單線程、多線程 要認(rèn)識多線程就要從操作系統(tǒng)的原理說起。 以前古老的DOS操作系統(tǒng)(V 6....
    GT921閱讀 1,024評論 0 3
  • 1 多線程 1.1 多線程介紹 ??學(xué)習(xí)多線程之前,我們先要了解幾個關(guān)于多線程有關(guān)的概念。??進(jìn)程:進(jìn)程指正在運行...
    圣堂刺客_x閱讀 355評論 0 0