Java進(jìn)程/線程的創(chuàng)建與銷毀

接觸java開發(fā)或者Android開發(fā)的時(shí)候,必不可少的會(huì)接觸到進(jìn)程、線程這樣的概念和知識(shí),那么進(jìn)程和線程到底是什么,又有什么樣的關(guān)聯(lián)以及有什么特點(diǎn)呢?

概述

進(jìn)程和線程,分別對(duì)應(yīng)的英文單詞是Process和Thread。

先說進(jìn)程,我們一般使用的電腦或者手機(jī)在運(yùn)行的時(shí)候,運(yùn)行的每一個(gè)程序之間相互不影響,當(dāng)一個(gè)程序異常崩潰之后,其他程序可以繼續(xù)執(zhí)行。簡(jiǎn)單來理解,每一個(gè)程序運(yùn)行就是一個(gè)進(jìn)程的開啟。尤其是Android系統(tǒng),正常來說會(huì)給每一個(gè)app分配單獨(dú)的進(jìn)程,賦予一定大小的內(nèi)存資源,確保每一個(gè)app都是單獨(dú)運(yùn)營(yíng),彼此不影響。就相當(dāng)于操作系統(tǒng)在你運(yùn)行程序的時(shí)候,給你劃分了一塊地方,接下來你的程序就在這塊地方自己蹦跶,不要出來擾亂別人的地方。所以進(jìn)程之間是相互獨(dú)立的,資源不共享的。(Android因?yàn)槊恳粋€(gè)程序分配的內(nèi)存有限,超過內(nèi)存上限就會(huì)OOM,所以有一些app功能較多,為了更好的運(yùn)行,會(huì)申請(qǐng)另一個(gè)進(jìn)程,這樣一個(gè)app程序就可能對(duì)應(yīng)多個(gè)進(jìn)程,這個(gè)后續(xù)再說Android多進(jìn)程相關(guān)的內(nèi)容)

線程呢就是在這個(gè)進(jìn)程中,處理一個(gè)一個(gè)任務(wù)的,在線程的地盤上,你可以買汽水,可以吃飯、可以看電影等等。這些都是一個(gè)一個(gè)的線程。線程算作輕量級(jí)的進(jìn)程,是進(jìn)程的一個(gè)執(zhí)行單元,是CPU調(diào)度和分派的最小單元,一個(gè)進(jìn)程是有一個(gè)或多個(gè)線程組成的,同一個(gè)進(jìn)程的線程之間可以共享該進(jìn)程的資源,同時(shí)一個(gè)線程可以創(chuàng)建、撤銷另一個(gè)線程,比如你吃飯的時(shí)候可以去看電影或者取消播放電影。

特點(diǎn)和聯(lián)系

通過以上描述,進(jìn)程和線程的特點(diǎn)也大概提到了,在說明具體的特點(diǎn)前,我們需要有以下基本認(rèn)識(shí):

  • 目前所有的操作系統(tǒng)都是支持多任務(wù)處理的
  • 對(duì)于一個(gè)CPU來說,某一時(shí)間點(diǎn)只能處理一件事情
  • 處理器執(zhí)行速度較快,很多事情可以被快速輪換處理,所以在我們感知上只同時(shí)被處理的

進(jìn)程的特點(diǎn):

  • 獨(dú)立性

每一個(gè)進(jìn)程都是獨(dú)立的地盤、資源,相互之間不干涉。

  • 動(dòng)態(tài)性

進(jìn)程是程序的執(zhí)行過程,所以就會(huì)有被創(chuàng)建、被執(zhí)行、暫停、撤銷、銷毀等過程和狀態(tài)。

  • 并發(fā)性

并發(fā)性也可以理解為異步,多個(gè)進(jìn)程在系統(tǒng)的執(zhí)行需要由系統(tǒng)進(jìn)程管理統(tǒng)一調(diào)度以異步的方式通過一定的算法來保證各個(gè)進(jìn)程能夠協(xié)同共享使用CPU資源和其他資源。

線程的特點(diǎn)

  • 最小性

線程是CPU調(diào)度和分配的最小單元。

  • 動(dòng)態(tài)性

線程也是經(jīng)過創(chuàng)建、執(zhí)行、掛起、銷毀等過程和狀態(tài)。

  • 關(guān)聯(lián)性

線程依賴于進(jìn)程,每一個(gè)線程必然有一個(gè)父進(jìn)程。

  • 獨(dú)立性

線程之間也是獨(dú)立的,一個(gè)線程不知道父進(jìn)程中是否有其他線程。

  • 并發(fā)行

線程的執(zhí)行是搶占式的,每一個(gè)線程在執(zhí)行中都可能被掛起,騰出資源來執(zhí)行另一個(gè)線程。

區(qū)別和關(guān)聯(lián)

  • 進(jìn)程是資源分配的基本單位(劃分地盤),線程是處理器調(diào)度的基本單位(調(diào)度CPU等)。

  • 線程可以啟動(dòng)、撤銷另一個(gè)線程,一個(gè)進(jìn)程可以有多個(gè)線程搶占式式并發(fā)執(zhí)行,每一個(gè)線程在執(zhí)行中都可能被掛起,騰出處理器來執(zhí)行另一個(gè)線程。

  • 線程不能獨(dú)立執(zhí)行,線程依賴于進(jìn)程,一個(gè)線程必然有一個(gè)父進(jìn)程,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)主線程,同一個(gè)父進(jìn)程的線程共享該進(jìn)程資源(地盤)。

  • 線程之間是獨(dú)立的,一個(gè)線程不知道父進(jìn)程中是否有其他的線程存在。

  • 調(diào)動(dòng)使用CPU的是線程,在處理器運(yùn)行的是線程。

  • 同一進(jìn)程的多個(gè)線程共享全局變量、靜態(tài)變量,堆存儲(chǔ),每一個(gè)線程也有自己棧段,用來存儲(chǔ)局部或臨時(shí)變量。

進(jìn)程的創(chuàng)建、銷毀

創(chuàng)建

java中的進(jìn)程類是Process。前面已經(jīng)提到,每一個(gè)程序的運(yùn)行基本上都會(huì)開啟一個(gè)進(jìn)程,然后執(zhí)行進(jìn)程的主線程。所以很多時(shí)候,我們并不直接去使用代碼來創(chuàng)建一個(gè)進(jìn)程,而是交由系統(tǒng)來為每一個(gè)程序自動(dòng)創(chuàng)建進(jìn)程。執(zhí)行Java程序都伴隨著Java虛擬機(jī)的執(zhí)行,每一個(gè)程序都對(duì)應(yīng)著一個(gè)虛擬機(jī),可以理解為每一個(gè)虛擬機(jī)的啟動(dòng)就是一個(gè)進(jìn)程的啟動(dòng)(當(dāng)然其他語言就不是虛擬機(jī),但是也有進(jìn)程的概念),然后啟動(dòng)該進(jìn)程的主線程,由主線程啟動(dòng)其他線程。對(duì)應(yīng)的就是可執(zhí)行的Java程序中必不可少的一個(gè)main方法,main方法是程序的起點(diǎn),是程序的初始線程,其他線程都由它啟動(dòng)。(Android中啟動(dòng)機(jī)制和普通的java程序不一樣,所以沒有main方法,此處不多說)

通過代碼啟動(dòng)一個(gè)進(jìn)程的方法:在java.lang.Runtime類和Java.lang.ProcessBuilder可以來創(chuàng)建一個(gè)本地的進(jìn)程。

具體的使用方法如下(Java1.8):

//打開指定路徑的文件夾

Process p1 =Runtime.getRuntime().exec("open /Users/anonyper/Desktop/未命名文件夾");

Process p2 = new ProcessBuilder("open", "/Users/anonyper/Desktop/未命名文件夾").start();
  • Runtime.getRuntime()的其他方法如下:
Process exec(String command) 
Process exec(String [] cmdarray) 
Process exec(String [] cmdarrag, String [] envp) 
Process exec(String [] cmdarrag, String [] envp, File dir) 
Process exec(String cmd, String [] envp) 
Process exec(String command, String [] envp, File dir)

//可以傳入不同的參數(shù)、環(huán)境變量、以及定義執(zhí)行的目錄
  • Java.lang.ProcessBuilder的方法如下
public ProcessBuilder(String... var1) {...}
public ProcessBuilder(List<String> var1) {...}
public ProcessBuilder command(List<String> var1) {...}
public ProcessBuilder command(String... var1) {...}

//使用ProcessBuilder可以預(yù)先配置相關(guān)屬性再創(chuàng)建進(jìn)程,而且也可以在后續(xù)使用中隨著需要改變相關(guān)屬性,屬性修改之后對(duì)已創(chuàng)建的進(jìn)程沒影響,只影響后續(xù)的start方法創(chuàng)建的進(jìn)程。

銷毀

只要java虛擬機(jī)中還有普通線程(用戶線程)在執(zhí)行,那么虛擬機(jī)就不會(huì)停止,當(dāng)沒有普通線程時(shí),虛擬機(jī)中都是守護(hù)線程的話,則虛擬機(jī)(該進(jìn)程)就會(huì)退出。

  • 自行退出

我們創(chuàng)建一個(gè)基本的可執(zhí)行的Java程序,該程序中用戶線程執(zhí)行完之后,該進(jìn)程也就退出了。

//代碼一
public class ProcessTest {
    public static void main(String[] args) {
        System.out.print("我是main 方法");
    }
}

執(zhí)行結(jié)果:


我是main 方法
Process finished with exit code 0  //可以看到,進(jìn)程以code 0的結(jié)果結(jié)束了

//代碼二
public class ProcessTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int index = 0;
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    index++;
                    System.out.println("我是用戶線程2,用來計(jì)數(shù)的:" + index);
                    if (index == 5)
                        return;
                }
            }
        }).start();
        String name = null;
        name.length();
        System.out.println("我是main 方法");
    }
}

執(zhí)行結(jié)果:

Exception in thread "main" java.lang.NullPointerException
    at com.test.java.ProcessTest.main(ProcessTest.java:29)
我是用戶線程2,用來計(jì)數(shù)的:1
我是用戶線程2,用來計(jì)數(shù)的:2
我是用戶線程2,用來計(jì)數(shù)的:3
我是用戶線程2,用來計(jì)數(shù)的:4
我是用戶線程2,用來計(jì)數(shù)的:5

Process finished with exit code 1 // main線程遇到異常退出了,但是整個(gè)進(jìn)程還未退出,直到線程2退出了才退出
  • 手動(dòng)結(jié)束

可以通過java.lang.System.exit(int code)方法結(jié)束(退出code為0代表正常退出,為其他代表異常):

//代碼一
public class ProcessTest {
    public static void main(String[] args) {
        System.out.print("11111");
        System.exit(0);
        System.out.print("22222");
    }
}

執(zhí)行結(jié)果:

11111

Process finished with exit code 0 // 可以看到,沒有打印最后的“22222”進(jìn)程就退出了


//代碼二
public class ProcessTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int index = 0;
                while (true) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    index++;
                    System.out.println("我是用戶線程2,用來計(jì)數(shù)的:" + index);
                    if (index == 5)
                        return;
                }
            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.exit(0);
        System.out.println("我是main 方法");
    }
}

執(zhí)行結(jié)果:

我是用戶線程2,用來計(jì)數(shù)的:1

Process finished with exit code 0 // 線程2還未執(zhí)行完,整個(gè)進(jìn)程就退出了

線程的創(chuàng)建和銷毀

創(chuàng)建和啟動(dòng)

java中的線程是Thread類,所以所有的線程對(duì)象都是Thread類或者其子類。java.lang.Thread類如下:

public class Thread implements Runnable {
    ...
    private Runnable target;
    ...
    public static native void sleep(long var0) throws InterruptedException;
    ...
    private void init(ThreadGroup var1, Runnable var2, String var3, long var4) {
        this.init(var1, var2, var3, var4, (AccessControlContext)null);
    }

    private void init(ThreadGroup var1, Runnable var2, String var3, long var4, AccessControlContext var6) {
        ...
        this.target = var2;
        ...
        }
    }
    ...
    public Thread() {
        this.init((ThreadGroup)null, (Runnable)null, "Thread-" + nextThreadNum(), 0L);
    }

    public Thread(Runnable var1) {
        this.init((ThreadGroup)null, var1, "Thread-" + nextThreadNum(), 0L);
    }

    Thread(Runnable var1, AccessControlContext var2) {
        this.init((ThreadGroup)null, var1, "Thread-" + nextThreadNum(), 0L, var2);
    }

    public Thread(ThreadGroup var1, Runnable var2) {
        this.init(var1, var2, "Thread-" + nextThreadNum(), 0L);
    }

    public Thread(String var1) {
        this.init((ThreadGroup)null, (Runnable)null, var1, 0L);
    }

    public Thread(ThreadGroup var1, String var2) {
        this.init(var1, (Runnable)null, var2, 0L);
    }

    public Thread(Runnable var1, String var2) {
        this.init((ThreadGroup)null, var1, var2, 0L);
    }

    public Thread(ThreadGroup var1, Runnable var2, String var3) {
        this.init(var1, var2, var3, 0L);
    }

    public Thread(ThreadGroup var1, Runnable var2, String var3, long var4) {
        this.init(var1, var2, var3, var4);
    }
    ...
    public void run() {
        if(this.target != null) {
            this.target.run();
        }
    }
    ...
    
}

Runnable接口代碼:

package java.lang;

@FunctionalInterface
public interface Runnable {
    void run();
}

通過以上代碼,可以看出來,想要?jiǎng)?chuàng)建一個(gè)線程有以下兩種方法:

  • 通過繼承Thread類或其子類,在run方法中加入自己的邏輯代碼,實(shí)例化該類對(duì)象來創(chuàng)建Thread對(duì)象
public class ThreadTest {
    public static void main(String[] args) {
        System.out.println("當(dāng)前線程name: " + Thread.currentThread().getName());
        new MyThread().start();
    }

}

class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("當(dāng)前線程name: " + getName());
    }
}

//執(zhí)行結(jié)果:

當(dāng)前線程name: main
當(dāng)前線程name: Thread-0

Process finished with exit code 0
  • 實(shí)現(xiàn)Runnable接口,在run方法中加入自己的邏輯代碼,將其傳遞給Thread構(gòu)造函數(shù),來創(chuàng)建Thread對(duì)象
public class ThreadTest {
    public static void main(String[] args) {
        System.out.println("當(dāng)前線程name: " + Thread.currentThread().getName());
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("當(dāng)前線程name: " + Thread.currentThread().getName());
            }
        }).start();
    }

}

//執(zhí)行結(jié)果:

當(dāng)前線程name: main
當(dāng)前線程name: Thread-0

Process finished with exit code 0
  • RunnableFuture接口是Runnable接口的子類,可以通過RunnableFuture的實(shí)現(xiàn)類FutureTask來作為Thread的target。
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

public class FutureTask<V> implements RunnableFuture<V> {
    ...
    public FutureTask(Callable<V> var1) {
        if(var1 == null) {
            throw new NullPointerException();
        } else {
            this.callable = var1;
            this.state = 0;
        }
    }

    public FutureTask(Runnable var1, V var2) {
        this.callable = Executors.callable(var1, var2);
        this.state = 0;
    }
    ...
}

public interface Callable<V> {
    V call() throws Exception;
}

通過以上代碼,我們可以實(shí)現(xiàn)一個(gè)Callable接口,實(shí)現(xiàn)起call方法作為FutureTask的執(zhí)行體,然后再將該FutureTask對(duì)象作為Thread的target以此來實(shí)現(xiàn)線程:

public class ThreadTest {
    public static void main(String[] args) {
        FutureTask futureTask = new FutureTask(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                sleep(5000);
                System.out.println("FutureTask 等待5秒后返回100");
                return 100;
            }
        });
        Thread thread = new Thread(futureTask, "futureTask");
        thread.start();
        try {
            System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
            System.out.println("futureTask.isCancelled():" + futureTask.isDone());
            System.out.println(futureTask.get());
            System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
            System.out.println("futureTask.isCancelled():" + futureTask.isDone());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

//執(zhí)行結(jié)果:

futureTask.isCancelled():false
futureTask.isCancelled():false
FutureTask 等待5秒后返回100
100
futureTask.isCancelled():false
futureTask.isCancelled():true

Process finished with exit code 0 // 結(jié)果值在5秒后返回,后續(xù)的邏輯代碼才執(zhí)行


public class ThreadTest {
    public static void main(String[] args) {
        FutureTask futureTask = new FutureTask(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                sleep(5000);
                System.out.println("FutureTask 等待5秒后返回100");
                String name = null;
                name.length();
                return 100;
            }
        });
        Thread thread = new Thread(futureTask, "futureTask");
        thread.start();
        try {
            System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
            System.out.println("futureTask.isCancelled():" + futureTask.isDone());
            System.out.println(futureTask.get());
            System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
            System.out.println("futureTask.isCancelled():" + futureTask.isDone());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

//執(zhí)行結(jié)果:

futureTask.isCancelled():false
futureTask.isCancelled():false
java.util.concurrent.ExecutionException: java.lang.NullPointerException
    at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    at com.test.java.ThreadTest.main(ThreadTest.java:33)
Caused by: java.lang.NullPointerException
    at com.test.java.ThreadTest$1.call(ThreadTest.java:24)
    at com.test.java.ThreadTest$1.call(ThreadTest.java:18)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.lang.Thread.run(Thread.java:745)
FutureTask 等待5秒后返回100

Process finished with exit code 0
    
//修改代碼:  
try {
            System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
            System.out.println("futureTask.isCancelled():" + futureTask.isDone());
            //System.out.println(futureTask.get());
            System.out.println("futureTask.isCancelled():" + futureTask.isCancelled());
            System.out.println("futureTask.isCancelled():" + futureTask.isDone());
        } catch (Exception e) {
            e.printStackTrace();
        }

//執(zhí)行結(jié)果:

futureTask.isCancelled():false
futureTask.isCancelled():false
futureTask.isCancelled():false
futureTask.isCancelled():false
FutureTask 等待5秒后返回100

Process finished with exit code 0

Callable接口相比Runnable接口,call方法會(huì)有返回值,并且在程序錯(cuò)誤時(shí)拋出異常。在使用futureTask.get()獲取返回值時(shí),當(dāng)前線程會(huì)等待結(jié)果的返回后才會(huì)往下執(zhí)行。如果call方法拋出異常,那么調(diào)用futureTask.get()的地方就需要捕獲異常,要不當(dāng)前線程就會(huì)中斷。

總結(jié)

以上兩種(callable和runnable算一種)方法區(qū)別是通過繼承Thread類的方法創(chuàng)建線程時(shí),多個(gè)線程之間無法共享線程類內(nèi)的實(shí)例變量,但是通過Runnable接口創(chuàng)建的線程,因?yàn)閞unnable接口只是作為Thread對(duì)象的一個(gè)target,然后調(diào)用runnable接口的run方法作為線程執(zhí)行體,多個(gè)線程可以共享一個(gè)target,這樣多個(gè)線程之間就可以共享一個(gè)實(shí)例變量了。同時(shí)使用接口的方式,該線程類除了實(shí)現(xiàn)Runnable和Callable接口外,還可以繼承其他類。

public class ThreadTest {
    public static void main(String[] args) {
        SellRunnable sellRunnable = new SellRunnable();
        Thread thread1 = new Thread(sellRunnable, "1");
        Thread thread2 = new Thread(sellRunnable, "2");
        thread1.start();
        thread2.start();
    }

}

class SellRunnable implements Runnable {
    //有十張票
    int index = 10;

    public synchronized void sell() {
        index--;
        System.out.println("售貨窗口:" + Thread.currentThread().getName() + "  賣出了一張票,剩余:" + index);
    }

    @Override
    public void run() {

        while (index > 0) {
            sell();
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}



//執(zhí)行結(jié)果:

售貨窗口:1  賣出了一張票,剩余:9
售貨窗口:2  賣出了一張票,剩余:8
售貨窗口:2  賣出了一張票,剩余:7
售貨窗口:1  賣出了一張票,剩余:6
售貨窗口:1  賣出了一張票,剩余:5
售貨窗口:2  賣出了一張票,剩余:4
售貨窗口:1  賣出了一張票,剩余:3
售貨窗口:2  賣出了一張票,剩余:2
售貨窗口:1  賣出了一張票,剩余:1
售貨窗口:2  賣出了一張票,剩余:0

Process finished with exit code 0

線程的結(jié)束

每一個(gè)線程都是一個(gè)順序執(zhí)行的代碼段,線程的結(jié)束有以下幾種方式:

  • run所有代碼邏輯執(zhí)行完成,線程結(jié)束
  • 線程在執(zhí)行過程中拋出了一個(gè)異?;蛘遝rror,線程結(jié)束
  • 調(diào)用了該線程的stop、resume、suspend或者Runtime.runFinalizersOnExit這幾個(gè)暴力方法,線程結(jié)束

使用stop等方法雖然可以強(qiáng)制結(jié)束線程,但是就如突然關(guān)掉電腦電源一樣,這個(gè)操作是不安全的,它不會(huì)保證線程的資源被正確釋放,所以這些方法已被廢棄。

  • 使用interrupt方法終端線程

使用interrupt方法不會(huì)真正的讓線程停止,調(diào)用之后thread對(duì)象的isInterrupted()或Thread.interrupted()方法返回一個(gè)false,通過這個(gè)變量我們?cè)O(shè)置break或者return的方式使得線程退出。

使用interrupt方法會(huì)有兩種情況,一種就是線程處于阻塞狀態(tài),比如sleep中,或者因?yàn)橥芥i的原因等待其他線程釋放資源,如果這是調(diào)用interrupt就會(huì)拋出InterruptException異常,捕獲這個(gè)異常后可以退出線程。另一種情況就是沒有阻塞的線程,需要獲取isInterrupted()/Thread.interrupted()值判斷是否需要退出線程,其實(shí)原理和第一種邏輯代碼執(zhí)行完退出一樣。

public class ThreadTest {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int index = 0;
                while (index <= 3) {
                    System.out.println("線程1 測(cè)試代碼執(zhí)行完畢" + " index = " + index);
                    index++;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }, "線程1");
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int index = 0;

                while (index <= 5) {
                    System.out.println("線程2 測(cè)試異常" + " index = " + index);
                    index++;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (index == 2) {
                        String name = null;
                        name.length();
                    }
                }
            }
        }, "線程2");
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                int index = 0;
                while (index <= 15) {
                    System.out.println("線程3 測(cè)試stop" + " index = " + index);
                    index++;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        return;
                    }
                }
            }
        }, "線程3");
        Thread thread4 = new Thread(new Runnable() {
            @Override
            public void run() {
                int index = 0;
                while (!Thread.interrupted()) {
                    index++;
                }
                System.out.println("線程4 測(cè)試interrupt" + " index = " + index);
            }
        }, "線程4");
        //啟動(dòng)線程
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        //標(biāo)示是否打印過已結(jié)束判斷
        boolean flag1 = false;
        boolean flag2 = false;
        boolean flag3 = false;
        boolean flag4 = false;
        //標(biāo)示是否調(diào)用stop方法
        boolean stop_flag = false;
        while (true) {
            boolean thread_flag1 = thread1.isAlive();
            boolean thread_flag2 = thread2.isAlive();

            if (!thread_flag1 && !flag1) {
                flag1 = true;
                System.out.println("!!!!線程1結(jié)束了");
            }
            if (!thread_flag2 && !flag2) {
                flag2 = true;
                System.out.println("!!!!線程2結(jié)束了");
            }
            if (!thread_flag1 && !thread_flag2 && !stop_flag) {
                stop_flag = true;
                System.out.println("調(diào)用stop和interrupt方法");
                thread3.stop();
                thread4.interrupt();

            }
            boolean thread_flag3 = thread3.isAlive();
            boolean thread_flag4 = thread4.isAlive();
            if (!thread_flag3 && !flag3) {
                flag3 = true;
                System.out.println("!!!!線程3結(jié)束了");
            }
            if (!thread_flag4 && !flag4) {
                flag4 = true;
                System.out.println("!!!!線程4結(jié)束了");
            }
            if (!thread_flag1 && !thread_flag2 && !thread_flag3 && !thread_flag4) {
                System.out.println("!!!!所有線程都結(jié)束了");
                return;
            }

        }

    }

}

//執(zhí)行結(jié)果

線程1 測(cè)試代碼執(zhí)行完畢 index = 0
線程2 測(cè)試異常 index = 0
線程3 測(cè)試stop index = 0
線程1 測(cè)試代碼執(zhí)行完畢 index = 1
線程2 測(cè)試異常 index = 1
線程3 測(cè)試stop index = 1
線程3 測(cè)試stop index = 2
線程1 測(cè)試代碼執(zhí)行完畢 index = 2
!!!!線程2結(jié)束了
Exception in thread "線程2" java.lang.NullPointerException
    at com.test.java.ThreadTest$2.run(ThreadTest.java:42)
    at java.lang.Thread.run(Thread.java:745)
線程3 測(cè)試stop index = 3
線程1 測(cè)試代碼執(zhí)行完畢 index = 3
線程3 測(cè)試stop index = 4
!!!!線程1結(jié)束了
調(diào)用stop和interrupt方法
!!!!線程3結(jié)束了
線程4 測(cè)試interrupt index = 750987062
!!!!線程4結(jié)束了
!!!!所有線程都結(jié)束了

Process finished with exit code 0

以上,就是進(jìn)程、線程的概念以及創(chuàng)建和結(jié)束方法。
進(jìn)程與線程的創(chuàng)建和銷毀
線程知識(shí)點(diǎn)總結(jié)
線程同步

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 熟悉歷史的朋友都知道,古代皇帝結(jié)婚年齡普遍是比較早的,基本上維持在十三到十七歲之間。比如,三國(guó)時(shí)期皇帝的平均結(jié)婚年...
    史趣閱讀 3,695評(píng)論 0 0
  • 傳統(tǒng)雞湯 雞湯是一道傳統(tǒng)的湯菜,制作原料主要有雞、水、調(diào)料等。雞湯特別是老母雞湯向來以美味著稱,雞湯還可以起到...
    隨意說_隨意寫閱讀 257評(píng)論 0 0
  • 企業(yè)名稱:海口美蘭美購(gòu)城實(shí)業(yè)有限公司 【時(shí)間】始于2018/5/1持續(xù)于2019/4/30 日精進(jìn)打卡第267天 ...
    楊慧裕閱讀 166評(píng)論 0 0
  • 最近的消息滿滿的都是勵(lì)志。彬哥放棄本校的保研資格考研浙大,剛剛得知被擬錄取的消息。他吐槽說復(fù)試那天他緊張的雙手發(fā)抖...
    喬納夫閱讀 3,440評(píng)論 5 6
  • 下定決心學(xué)寫作是看了弘丹老師《弘丹學(xué)寫作這本書》,受到每日清晨寫作的啟發(fā),自己一起來頭一件事就開始投入寫作,...
    樂楓_96e7閱讀 228評(píng)論 0 1

友情鏈接更多精彩內(nèi)容