Java多執行緒-基礎篇

weixin_34208283發表於2018-10-17
11725840-8d53073ed3ccf876.jpg
qaq

學習訊息機制後,有幾個關於執行緒的問題。於是有了這篇文章。本文絕大多數是對於 Java多執行緒程式設計核心技術的總結

什麼是執行緒

執行程式中的一個執行緒,Java虛擬機器允許應用程式同時執行多個執行執行緒。每個執行緒都有優先權。具有較高優先順序的執行緒優先於具有較低優先順序的執行緒執行。每個執行緒可能會被標記為守護程式。當在某個執行緒中執行的程式碼建立一個新Thread物件時,新執行緒的優先順序最初設定為等於建立執行緒的優先順序,並且當且僅當建立執行緒是守護程式時才是守護程式執行緒。

執行緒和程式的關係

11725840-9be55b2834dc50e1.png
一個程式中可以包含多個執行緒

Thread的狀態(有待補充)

11725840-242a57a3e7ff3f11.png
Thread狀態

執行緒的啟動

如何實現Thread

* public class Thread implements Runnable
* public interface Runnable
繼承Thread或者實現Runnable介面
public interface Runnable {
    public abstract void run();
}

我們來看一下Runnable實現類Thread中的run方法。

@Override
public void run() {
    if (target != null) {//target是如何初始化?
        target.run();
    }
}

//執行緒初始化
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    //currentThread():返回當前正在執行執行緒的引用
    Thread parent = currentThread();
    if (g == null) {
        g = parent.getThreadGroup();//執行緒終止返回null,否則返回ThreadGroup物件
    }
    
    g.addUnstarted();//記錄未啟動的執行緒
    this.group = g;

    this.target = target;
    //執行緒的優先順序具有繼承關係
    this.priority = parent.getPriority();
    //是否是守護執行緒(後面會提到)
    this.daemon = parent.isDaemon();
    setName(name);
    
    init2(parent);

    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;
    tid = nextThreadID();
}

我們看一下Thread的初始化。

public Thread() {
    //target為null,run()方法怎麼執行?
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

通過上面的分析我們知道了Thread初始化執行了那些操作。但是run方法是怎麼被執行的?
我們看一下start()方法原始碼。

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    //不能多次呼叫start()方法,否則丟擲IllegalThreadStateException
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    //新增執行緒到ThreadGroup中
    group.add(this);

    started = false;
    try {
        //呼叫run方法
        nativeCreate(this, stackSize, daemon);
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

我們看到最終都會去呼叫c++層的nativeCreate()。

static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size,
                                jboolean daemon) {
    .
    .
    .
        Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE);
}

CreateNativeThread(JNIEnv*env, jobject java_peer, size_t stack_size, bool is_daemon) {
    .
    .
    .
    if (child_jni_env_ext.get() != nullptr) {
        pthread_t new_pthread;
        pthread_attr_t attr;
        child_thread -> tlsPtr_.tmp_jni_env = child_jni_env_ext.get();
        CHECK_PTHREAD_CALL(pthread_attr_init, ( & attr), "new thread");
        CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, ( & attr, PTHREAD_CREATE_DETACHED),
                "PTHREAD_CREATE_DETACHED");
        CHECK_PTHREAD_CALL(pthread_attr_setstacksize, ( & attr, stack_size), stack_size);
        pthread_create_result = pthread_create( & new_pthread,
        &attr,
                Thread::CreateCallback,
                child_thread);
        CHECK_PTHREAD_CALL(pthread_attr_destroy, ( & attr), "new thread");

        if (pthread_create_result == 0) {
            // pthread_create started the new thread. The child is now responsible for managing the
            // JNIEnvExt we created.
            // Note: we can't check for tmp_jni_env == nullptr, as that would require synchronization
            //       between the threads.
            child_jni_env_ext.release();
            return;
        }
    }

    // Either JNIEnvExt::Create or pthread_create(3) failed, so clean up.
    {
        MutexLock mu (self,*Locks::runtime_shutdown_lock_);
        runtime -> EndThreadBirth();
    }
    .
    .
    .
}
CreateCallback(void*arg) {

    .
    .
    .
    // Invoke the 'run' method of our java.lang.Thread.
ObjPtr<mirror::Object > receiver = self -> tlsPtr_.opeer;
//呼叫Thread的run方法(為什麼不直接呼叫run()?)
    jmethodID mid = WellKnownClasses::java_lang_Thread_run;
    ScopedLocalRef<jobject> ref (soa.Env(), soa.AddLocalReference < jobject > (receiver));
    InvokeVirtualOrInterfaceWithJValues(soa, ref.get(), mid, nullptr);
    // Detach and delete self.
    Runtime::Current () -> GetThreadList()->Unregister(self);

    return nullptr;
}

run方法被那個呼叫?

11725840-e664e939591c43e7.png

為什麼不直接呼叫run方法?(start()與run()的區別)
Java中start與run
1)Thread狀態來說
Start():新建狀態---就緒狀態
Run():新建狀態--執行狀態
2)能否新建執行緒來說
Start()可以新建執行緒。run()則不行
3)能否進行執行緒切換
start中的run程式碼可以進行執行緒切換。
run方法必須等待其程式碼全部執行完才能繼續執行。

執行緒安全性問題

11725840-d70d4bfe562f0432.png
public class NotSharingThread extends Thread{

    private int count = 5;

    public NotSharingThread(String threadName) {
        super();
        this.setName(threadName);
    }

    @Override
    public void run() {
        super.run();
        while (count > 0){
            count --;
            System.out.println("由" + this.currentThread().getName()
                    + "計算,count = " + count);
        }
    }
}

NotSharingThread a = new NotSharingThread("A");
NotSharingThread b = new NotSharingThread("B");
NotSharingThread c = new NotSharingThread("C");
a.start();
b.start();
c.start();

I/System.out: 由A計算,count = 4
I/System.out: 由A計算,count = 3
I/System.out: 由A計算,count = 2
I/System.out: 由A計算,count = 1
I/System.out: 由A計算,count = 0
I/System.out: 由B計算,count = 4
I/System.out: 由B計算,count = 3
I/System.out: 由B計算,count = 2
I/System.out: 由B計算,count = 1
I/System.out: 由B計算,count = 0
I/System.out: 由C計算,count = 4
I/System.out: 由C計算,count = 3
I/System.out: 由C計算,count = 2
I/System.out: 由C計算,count = 1
I/System.out: 由C計算,count = 0
11725840-190acc7ecfeaabcf.png
public class LoginServlet {

    private static String userNameRef;
    private static String passwordRef;

    public static void doPost(String userName,String password){
        try{
            userNameRef = userName;
            if(userName.equals("a")) {
                Thread.sleep(5000);
            }
            passwordRef = password;
            System.out.println("userName = " + userNameRef +
                    "passWord = " + passwordRef);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

}
public class ALoginThread  extends Thread{

    @Override
    public void run() {
        LoginServlet.doPost("a","aa");
    }
}
public class BLoginThread extends Thread{
    @Override
    public void run() {
        LoginServlet.doPost("b","bb");
    }
}

ALoginThread aLoginThread = new ALoginThread();
aLoginThread.start();
BLoginThread bLoginThread = new BLoginThread();
bLoginThread.start();
I/System.out: userName = b passWord = bb
I/System.out: userName = b passWord = aa

通過上述程式碼我們發現,同一時間多個執行緒對同一資料進行操作。引發了“非執行緒安全”。
通過synchronized方法可以避免“非執行緒安全”。
synchronized簡介

執行緒停止

Java中有三種方式退出正在執行的執行緒:
1)使用退出標誌,使執行緒正常退出,當run方法完成後執行緒終止。
2)使用stop方法強行終止執行緒(不推薦)。
3)使用interrupt方法中斷執行緒(推薦)。
Thread.interrupt()
並不會終止一個正在執行的執行緒,還需要加入一個判斷才可以完成執行緒的停止。(interrupt()方法僅僅是在當前執行緒中打了一個停止的標記)

判斷執行緒是否是停止狀態
this.interrupted():測試當前執行緒是否已經是中斷狀態。執行後具有將狀態標誌清除為false的功能(前提是中斷狀態)。
This.isInterrupted():測試執行緒Thread物件是否已經是中斷狀態,但不清除狀態標誌。

如何終止執行緒舉例(推薦使用try-catch)

public class InterruptedThread extends Thread{
    
    @Override
    public void run() {
        super.run();
        try{
            for (int i = 0 ; i < 500000 ; i ++){
                if(this.isInterrupted()) {//判斷是否是中斷標誌
                    System.out.println("已經是停止狀態了!我要退出了");
                    throw new InterruptedException();
                }
                System.out.println("i = " + (i + 1));
            }
            System.out.println("我還在執行");
        }catch (InterruptedException e){
            System.out.println("進入try-catch方法");
            e.printStackTrace();
        }

    }

}
try{
    InterruptedThread interruptedThread = new InterruptedThread();
    interruptedThread.start();
    Thread.sleep(5000);
    //設定中斷的標記位
    interruptedThread.interrupt();
}catch (InterruptedException exception){
    System.out.println("主方法try-catch");
    exception.printStackTrace();
}
輸出結果
I/System.out: i = 41998
I/System.out: 已經是停止狀態了!我要退出了
I/System.out: 進入try-catch方法

執行緒暫停(恢復)

暫停執行緒意味著此執行緒還可以恢復執行。suspend()暫停執行緒,resume()恢復執行緒。

執行緒優先順序(1-10)

執行緒優先順序具有繼承性。優先順序高的執行緒得到的CPU資源較多。但並不能保證優先順序高德執行緒先於優先順序低的執行緒執行完任務。通過setPriority()可以設定優先順序。

常見方法

currentThread()

/**
 * Returns a reference to the currently executing thread object.
 *
 * @return  the currently executing thread.
 */
//得當當前正在執行的執行緒
public static native Thread currentThread();

public class RunOrStartThread extends Thread {

    public RunOrStartThread() {
        System.out.println("構造方法列印:" + Thread.currentThread().getName());
    }

    @Override
    public void run() {
        System.out.println("run方法列印: "  + Thread.currentThread().getName());
    }
}
//  測試
        RunOrStartThread runOrStartThread = new RunOrStartThread();
//        runOrStartThread.start();
//        I/System.out: 構造方法列印:main
//        I/System.out: run方法列印: Thread-142

//        runOrStartThread.run();
//        I/System.out: 構造方法列印:main
//        I/System.out: run方法列印: main

isAlive()
判斷當前執行緒是否處於活動狀態

sleep()
指定毫秒數內,暫停當前正在執行的執行緒。

getId()
獲取執行緒的唯一標識。

yield方法
Yield()方法的作用是放棄當前CPU的資源,讓給其他執行緒。

Java中有兩種執行緒:
使用者執行緒和守護執行緒(具有陪護功能,典型的守護執行緒垃圾回收器)。當程式中不存在非守護執行緒了,守護執行緒自動銷燬。通過setDaemon()設定守護執行緒。

執行緒間通訊

wait/notify

wait/notify:必須出現在同步方法或者同步程式碼塊中;變數在特殊時刻需要特殊處理,避免CPU浪費。

wait:使當前執行緒進入預執行佇列,直到接收到通知或者執行緒被中斷為止。*具有釋放鎖的操作。

notify:隨機恢復擁有同一物件鎖的wait執行緒。notify並不馬上釋放鎖直到synchronized程式碼執行完後才釋放。

使用管道流實現執行緒通訊

public class WriteData {

    public void writeMethod(PipedOutputStream out){
        try{
            Log.e("TAG", "write:");
            for (int i = 0 ; i < 300 ; i++){
                String outData = (i+1) + "";
                out.write(outData.getBytes());
                Log.e("TAG", "" + outData);
            }
            out.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

}
public class ReadData {

    public void readMethod(PipedInputStream input) {
        try{
            Log.e("TAG", "read:");
            byte[] bytes = new byte[1024];
            int read = input.read(bytes);
            while(read != -1) {
                String s = new String(bytes, 0, read);
                Log.e("TAG", "" + s);
                read = input.read(bytes);
            }
            input.close();

        }catch (IOException e){
            e.printStackTrace();
        }
    }

}
public class WriteThread extends Thread {

    private WriteData data;
    private PipedOutputStream outputStream;

    public WriteThread(WriteData data, PipedOutputStream outputStream) {
        this.data = data;
        this.outputStream = outputStream;
    }

    @Override
    public void run() {
        super.run();
        data.writeMethod(outputStream);
    }
}
public class ReadThread extends Thread{

    private ReadData readData;
    private PipedInputStream inputStream;

    public ReadThread(ReadData readData, PipedInputStream inputStream) {
        this.readData = readData;
        this.inputStream = inputStream;
    }

    @Override
    public void run() {
        super.run();
        readData.readMethod(inputStream);
    }
}
//熟悉流
try{
    WriteData writeData = new WriteData();
    ReadData readData = new ReadData();
    PipedOutputStream pipedOutputStream = new PipedOutputStream();
    PipedInputStream pipedInputStream = new PipedInputStream();
    //使兩個Stream之間產生通訊連線
    pipedOutputStream.connect(pipedInputStream);

    ReadThread readThread = new ReadThread(readData, pipedInputStream);
    WriteThread writeThread = new WriteThread(writeData, pipedOutputStream);

    writeThread.start();
    Thread.sleep(500);
    readThread.start();
}catch (IOException e){
    e.printStackTrace();
} catch (InterruptedException e) {
    e.printStackTrace();
}

join方法

等待當前執行緒銷燬。內部通過wait方法實現(執行緒終止時呼叫notifyAll方法)

public class MyThread extends Thread {

    public volatile static int count;

    private static void addCount(){
        for (int i = 0 ; i < 100 ; i ++){
            count ++;
        }
        Log.e("TAG", "count = " + count);
    }

    @Override
    public void run() {
        addCount();
    }
}
MyThread myThread = new MyThread();
myThread.start();

Log.e("TAG", "1111111111111111111");
Log.e("TAG", "222222222222222222");
Log.e("TAG", "3333333333333333333333");

//TAG: 1111111111111111111
//TAG: 222222222222222222
//TAG: 3333333333333333333333
//TAG: count = 100

//加入join後(join具有釋放鎖的作用)
//TAG: count = 100
//TAG: 1111111111111111111
//TAG: 222222222222222222
//TAG: 3333333333333333333333

相關文章