Java多線程-基礎篇

qaq

學習消息機制后,有幾個關于線程的問題。于是有了這篇文章。本文絕大多數是對于 Java多線程編程核心技術的總結

什么是線程

執(zhí)行程序中的一個線程,Java虛擬機允許應用程序同時運行多個執(zhí)行線程。每個線程都有優(yōu)先權。具有較高優(yōu)先級的線程優(yōu)先于具有較低優(yōu)先級的線程執(zhí)行。每個線程可能會被標記為守護進程。當在某個線程中運行的代碼創(chuàng)建一個新Thread對象時,新線程的優(yōu)先級最初設置為等于創(chuàng)建線程的優(yōu)先級,并且當且僅當創(chuàng)建線程是守護進程時才是守護進程線程。

線程和進程的關系

一個進程中可以包含多個線程

Thread的狀態(tài)(有待補充)

Thread狀態(tài)

線程的啟動

如何實現(xiàn)Thread

* public class Thread implements Runnable
* public interface Runnable
繼承Thread或者實現(xiàn)Runnable接口
public interface Runnable {
    public abstract void run();
}

我們來看一下Runnable實現(xiàn)類Thread中的run方法。

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

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

    this.target = target;
    //線程的優(yōu)先級具有繼承關系
    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()方法怎么執(zhí)行?
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

通過上面的分析我們知道了Thread初始化執(zhí)行了那些操作。但是run方法是怎么被執(zhí)行的?
我們看一下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方法被那個調用?


為什么不直接調用run方法?(start()與run()的區(qū)別)
Java中start與run
1)Thread狀態(tài)來說
Start():新建狀態(tài)---就緒狀態(tài)
Run():新建狀態(tài)--運行狀態(tài)
2)能否新建線程來說
Start()可以新建線程。run()則不行
3)能否進行線程切換
start中的run代碼可以進行線程切換。
run方法必須等待其代碼全部執(zhí)行完才能繼續(xù)執(zhí)行。

線程安全性問題

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
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

通過上述代碼我們發(fā)現(xiàn),同一時間多個線程對同一數據進行操作。引發(fā)了“非線程安全”。
通過synchronized方法可以避免“非線程安全”。
synchronized簡介

線程停止

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

判斷線程是否是停止狀態(tài)
this.interrupted():測試當前線程是否已經是中斷狀態(tài)。執(zhí)行后具有將狀態(tài)標志清除為false的功能(前提是中斷狀態(tài))。
This.isInterrupted():測試線程Thread對象是否已經是中斷狀態(tài),但不清除狀態(tài)標志。

如何終止線程舉例(推薦使用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("已經是停止狀態(tài)了!我要退出了");
                    throw new InterruptedException();
                }
                System.out.println("i = " + (i + 1));
            }
            System.out.println("我還在執(zhí)行");
        }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: 已經是停止狀態(tài)了!我要退出了
I/System.out: 進入try-catch方法

線程暫停(恢復)

暫停線程意味著此線程還可以恢復運行。suspend()暫停線程,resume()恢復線程。

線程優(yōu)先級(1-10)

線程優(yōu)先級具有繼承性。優(yōu)先級高的線程得到的CPU資源較多。但并不能保證優(yōu)先級高德線程先于優(yōu)先級低的線程執(zhí)行完任務。通過setPriority()可以設置優(yōu)先級。

常見方法

currentThread()

/**
 * Returns a reference to the currently executing thread object.
 *
 * @return  the currently executing thread.
 */
//得當當前正在執(zhí)行的線程
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: 構造方法打?。簃ain
//        I/System.out: run方法打印: Thread-142

//        runOrStartThread.run();
//        I/System.out: 構造方法打?。簃ain
//        I/System.out: run方法打印: main

isAlive()
判斷當前線程是否處于活動狀態(tài)

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

getId()
獲取線程的唯一標識。

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

Java中有兩種線程:
用戶線程和守護線程(具有陪護功能,典型的守護線程垃圾回收器)。當進程中不存在非守護線程了,守護線程自動銷毀。通過setDaemon()設置守護線程。

線程間通信

wait/notify

wait/notify:必須出現(xiàn)在同步方法或者同步代碼塊中;變量在特殊時刻需要特殊處理,避免CPU浪費。

wait:使當前線程進入預執(zhí)行隊列,直到接收到通知或者線程被中斷為止。*具有釋放鎖的操作。

notify:隨機恢復擁有同一對象鎖的wait線程。notify并不馬上釋放鎖直到synchronized代碼執(zhí)行完后才釋放。

使用管道流實現(xiàn)線程通信

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方法實現(xiàn)(線程終止時調用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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容