進(jìn)程和多線程的概念

1 線程概念

1.1 進(jìn)程

  • 在現(xiàn)代的操作系統(tǒng)中勺爱,進(jìn)程是資源分配的最小單位迫吐,而線程是CPU調(diào)度的基本單位邮屁。
  • 一個(gè)進(jìn)程中最少有一個(gè)線程整袁,名叫主線程。進(jìn)程是程序執(zhí)行的一個(gè)實(shí)例佑吝,比如說(shuō)10個(gè)用戶(hù)同時(shí)執(zhí)行Chrome瀏覽器坐昙,那么就有10個(gè)獨(dú)立的進(jìn)程(盡管他們共享一個(gè)可執(zhí)行代碼)。

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

  • 每一個(gè)進(jìn)程都有自己的獨(dú)立的一塊內(nèi)存空間芋忿、一組資源系統(tǒng)民珍。其內(nèi)部數(shù)據(jù)和狀態(tài)都是完全獨(dú)立的。
  • 進(jìn)程的優(yōu)點(diǎn)是提高CPU的運(yùn)行效率盗飒,在同一個(gè)時(shí)間內(nèi)執(zhí)行多個(gè)程序嚷量,即并發(fā)執(zhí)行。但是從嚴(yán)格上將逆趣,也不是絕對(duì)的同一時(shí)刻執(zhí)行多個(gè)程序蝶溶,只不過(guò)CPU在執(zhí)行時(shí)通過(guò)時(shí)間片等調(diào)度算法不同進(jìn)程告訴切換。

所以總結(jié)來(lái)說(shuō):進(jìn)程由操作系統(tǒng)調(diào)度宣渗,簡(jiǎn)單而且穩(wěn)定抖所,進(jìn)程之間的隔離性好,一個(gè)進(jìn)程的奔潰不會(huì)影響其他進(jìn)程痕囱。單進(jìn)程編程簡(jiǎn)單田轧,在多個(gè)情況下可以把進(jìn)程和CPU進(jìn)行綁定,從分利用CPU鞍恢。

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

  • 一般來(lái)說(shuō)進(jìn)程消耗的內(nèi)存比較大傻粘,進(jìn)程切換代價(jià)很高,進(jìn)程切換也像線程一樣需要保持上一個(gè)進(jìn)程的上下文環(huán)境帮掉。
  • 比如在Web編程中弦悉,如果一個(gè)進(jìn)程處理一個(gè)請(qǐng)求的話,如果提高并發(fā)量就要提高進(jìn)程數(shù)蟆炊,而進(jìn)程數(shù)量受內(nèi)存和切換代價(jià)限制稽莉。

1.2 線程

  • 線程是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分配的基本單位涩搓,它比進(jìn)程更下能獨(dú)立運(yùn)行的基本單位污秆,線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中不可少的資源(如程序計(jì)數(shù)器昧甘、棧)良拼,但是它可與同屬一個(gè)進(jìn)程的其他線程共享進(jìn)程所擁有的全部資源。
  • 同類(lèi)的多線程共享一塊內(nèi)存空間個(gè)一組系統(tǒng)資源疾层,線程本身的數(shù)據(jù)通常只有CPU的寄存器數(shù)據(jù)将饺,以及一個(gè)供程序執(zhí)行的堆棧。
  • 線程在切換時(shí)負(fù)荷小,因此予弧,線程也稱(chēng)為輕負(fù)荷進(jìn)程刮吧。一個(gè)進(jìn)程中可以包含多個(gè)線程。
  • 在JVM中掖蛤,本地方法棧杀捻、虛擬機(jī)棧和程序計(jì)數(shù)器是線程隔離的,而堆區(qū)和方法區(qū)是線程共享的蚓庭。

1.3 進(jìn)程線程的區(qū)別

  • 地址空間:線程是進(jìn)程內(nèi)的一個(gè)執(zhí)行單元致讥;進(jìn)程至少有一個(gè)線程;一個(gè)進(jìn)程內(nèi)的多線程它們共享進(jìn)程的地址空間器赞;而進(jìn)程自己獨(dú)立的地址空間
  • 資源擁有:進(jìn)程是資源分配和擁有的單位垢袱,同一個(gè)進(jìn)程內(nèi)的線程共享進(jìn)程的資源。
  • 線程是處理器調(diào)度的基本單位港柜,但進(jìn)程不是
  • 二者均可并發(fā)執(zhí)行
  • 并發(fā):多個(gè)事件在同一個(gè)時(shí)間段內(nèi)一起執(zhí)行
  • 并行:多個(gè)事件在同一時(shí)刻同時(shí)執(zhí)行

1.4 多線程

  • 雖然多線程進(jìn)一步提高了應(yīng)用的執(zhí)行效率请契,但是由于線程之間會(huì)共享內(nèi)存資源,這會(huì)導(dǎo)致一些資源同步的問(wèn)題夏醉,另外爽锥,線程之間切換也會(huì)對(duì)資源有所消耗。

  • 如果一臺(tái)電腦只有一個(gè)CPU核心畔柔,那么多線也并沒(méi)有真正的"同時(shí)"運(yùn)行氯夷,它們之間需要通過(guò)相互切換來(lái)共享CPU核心,所以靶擦,只有一個(gè)CPU核心的情況下腮考,多線程不會(huì)提高應(yīng)用效率。

  • 但是奢啥,現(xiàn)在計(jì)算機(jī)一般都會(huì)有多個(gè)CPU秸仙,并且每個(gè)CPU可能還有會(huì)多個(gè)核心,所以現(xiàn)在硬件資源條件下桩盲,多線程編程可以極大的提高應(yīng)用的效率。


    5713484-0f7a599138b3862f.png
  • 在Java程序中席吴,JVM負(fù)責(zé)線程的調(diào)度赌结。線程調(diào)度是按照特定的機(jī)制為多個(gè)線程分配CPU的使用權(quán)。

  • 調(diào)度的模式有兩種:分時(shí)調(diào)度搶占式調(diào)度孝冒。分時(shí)調(diào)度是所有線程輪流獲得CPU使用權(quán)柬姚,并平均分配每個(gè)線程占用CPU的時(shí)間;搶占式調(diào)度是根據(jù)線程的優(yōu)先級(jí)別來(lái)獲取CPU的使用權(quán)庄涡。JVM的線程調(diào)度模式采用了搶占式模式量承。

2 Thread類(lèi)

Thread.java

public class Thread implements Runnable {
  .....
}

通過(guò)上面代碼,我們可以知道Thread實(shí)現(xiàn)了Runnable,側(cè)面也說(shuō)明線程是"可執(zhí)行的代碼"撕捍。

public  interface Runnable {
    public abstract void run();
}

Runnable是一個(gè)接口類(lèi)拿穴,唯一提供的方法就是run()。

2.1 Thread的使用

  • java的JDK自帶了對(duì)多線程技術(shù)的支持
  • 實(shí)現(xiàn)多線程變成主要有兩種
    一:繼承Thread類(lèi)--重寫(xiě)run方法忧风。在run方法中寫(xiě)線程要執(zhí)行的任務(wù)
    二:實(shí)現(xiàn)Runnable接口
  • Thread類(lèi)實(shí)現(xiàn)了Runanble接口默色,他們之間具有多態(tài)關(guān)系。
  • 使用繼承Thread類(lèi)來(lái)創(chuàng)建新線程狮腿,最大的局限就是不支持多繼承腿宰。所以為了支持多繼承,完全可以實(shí)現(xiàn)Runnable接口的方式缘厢。使用這兩種方式創(chuàng)建線程在工作時(shí)的性質(zhì)是一樣的吃度,沒(méi)有本質(zhì)區(qū)別。
  • 如果多次調(diào)用start()方法贴硫,則會(huì)出現(xiàn)異常Exception in thread"mian"java.lang.IllegalThreadStateException

異步和同步

  • Thread.java類(lèi)中start()方法通知“線程規(guī)劃器”此線程已經(jīng)準(zhǔn)備就緒椿每,等待調(diào)用線程對(duì)象的run()方法。這個(gè)過(guò)程就是讓系統(tǒng)安排一個(gè)時(shí)間來(lái)調(diào)用Thread中的run()方法夜畴,也就是使線程得到運(yùn)行拖刃,啟動(dòng)線程,具有異步執(zhí)行的效果贪绘。執(zhí)行start()方法的順序不代表線程啟動(dòng)的順序
  • 如果調(diào)用代碼thread.run()就不是異步執(zhí)行了兑牡,而是同步,那么此線程對(duì)象并不交給“線程規(guī)劃器”來(lái)進(jìn)行處理税灌,而是由main主線程來(lái)調(diào)用run()方法均函,也就是必須等run方法中的代碼執(zhí)行完才可以執(zhí)行后面的代碼。
  • Thread類(lèi)實(shí)現(xiàn)了Runnable接口--構(gòu)造方法不僅可以傳入Runnable接口的對(duì)象還可以傳入一個(gè)Thread類(lèi)的對(duì)象菱涤,這樣就可以將一個(gè)thread對(duì)象中的run()方法交由其他的線程進(jìn)行調(diào)用苞也。

2.2 Thread的常用方法:

5713484-4657ce630772034a.png

2.3 Thread的常用字段:

volatile ThreadGroup group;
    volatile boolean daemon;
    volatile String name;
    volatile int priority;
    volatile long stackSize;
    Runnable target;
    private static int count = 0;

    /**
     * Holds the thread's ID. We simply count upwards, so
     * each Thread has a unique ID.
     */
    private long id;

    /**
     * Normal thread local values.
     */
    ThreadLocal.Values localValues;
  • group:每一個(gè)線程都屬于一個(gè)group,當(dāng)線程被創(chuàng)建是粘秆,就會(huì)加入一個(gè)特定的group如迟,當(dāng)前程運(yùn)行結(jié)束,會(huì)從這個(gè)group移除攻走。
  • deamon:當(dāng)前線程是不是守護(hù)線程殷勘,守護(hù)線程只會(huì)在沒(méi)有非守護(hù)線程運(yùn)行下才會(huì)運(yùn)行
  • name:線程名稱(chēng)
  • priority:線程優(yōu)先級(jí),Thread的線程優(yōu)先級(jí)取值范圍為[1,10]昔搂,默認(rèn)優(yōu)先級(jí)為5
  • stackSize:線程棧大小玲销,默認(rèn)是0,即使用默認(rèn)的線程棧大小(由dalvik中的全局變量gDvm.stackSize決定)
  • target:一個(gè)Runnable對(duì)象摘符,Thread的run()方法中會(huì)轉(zhuǎn)調(diào)target的run()方法贤斜,這是線程真正處理事務(wù)的地方策吠。
  • id:線程id,通過(guò)遞增的count得到該id瘩绒,如果沒(méi)有顯式給線程設(shè)置名字猴抹,那么久會(huì)使用Thread+id當(dāng)前線程的名字。注意這里不是真正的線程id草讶,即在logcat中打印的tid并不是這個(gè)id洽糟,那tid是指dalvik線程id
  • localValues:本地線程存儲(chǔ)(TLS)數(shù)據(jù) 關(guān)于TLS后面會(huì)詳細(xì)介紹

2.4 create()方法:

為什么要研究create()方法?因?yàn)門(mén)hread一種有9個(gè)構(gòu)造函數(shù)堕战,其中8個(gè)里面最終都是調(diào)用了create()方法
Thread.java 402行

/**
     * Initializes a new, existing Thread object with a runnable object,
     * the given name and belonging to the ThreadGroup passed as parameter.
     * This is the method that the several public constructors delegate their
     * work to.
     *
     * @param group ThreadGroup to which the new Thread will belong
     * @param runnable a java.lang.Runnable whose method <code>run</code> will
     *        be executed by the new Thread
     * @param threadName Name for the Thread being created
     * @param stackSize Platform dependent stack size
     * @throws IllegalThreadStateException if <code>group.destroy()</code> has
     *         already been done
     * @see java.lang.ThreadGroup
     * @see java.lang.Runnable
     */
    private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {
        //步驟一 
        Thread currentThread = Thread.currentThread();

        //步驟二 
        if (group == null) {
            group = currentThread.getThreadGroup();
        }

        if (group.isDestroyed()) {
            throw new IllegalThreadStateException("Group already destroyed");
        }

        this.group = group;

        synchronized (Thread.class) {
            id = ++Thread.count;
        }

        if (threadName == null) {
            this.name = "Thread-" + id;
        } else {
            this.name = threadName;
        }

        this.target = runnable;
        this.stackSize = stackSize;

        this.priority = currentThread.getPriority();

        this.contextClassLoader = currentThread.contextClassLoader;

        // Transfer over InheritableThreadLocals.
        if (currentThread.inheritableValues != null) {
            inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues);
        }

        // add ourselves to our ThreadGroup of choice
        //步驟二 
        this.group.addThread(this);
    }

我把create內(nèi)部代碼大體上分為3個(gè)部分

  • 步驟一:通過(guò)靜態(tài)函數(shù)currentThread獲取創(chuàng)建線程所在的當(dāng)前線程
  • 步驟二:將創(chuàng)新線程所在的當(dāng)前線程的一些屬性賦值給即將創(chuàng)建的線程
  • 步驟三:通過(guò)調(diào)用ThreadGroup的addThread方法將新線程添加到group中坤溃。

2.4 Thread的生命周期:

線程共有6種狀態(tài);在某一時(shí)刻只能是這6種狀態(tài)之一嘱丢。這些狀態(tài)由Thread.State這個(gè)枚舉類(lèi)型表示薪介,并且可以通過(guò)getState()方法獲得當(dāng)前具體的狀態(tài)類(lèi)型。

/**
     * A representation of a thread's state. A given thread may only be in one
     * state at a time.
     */
    public enum State {
        /**
         * The thread has been created, but has never been started.
         */
        NEW,
        /**
         * The thread may be run.
         */
        RUNNABLE,
        /**
         * The thread is blocked and waiting for a lock.
         */
        BLOCKED,
        /**
         * The thread is waiting.
         */
        WAITING,
        /**
         * The thread is waiting for a specified amount of time.
         */
        TIMED_WAITING,
        /**
         * The thread has been terminated.
         */
        TERMINATED
    }
  • NEW : 起勁尚未啟動(dòng)的線程的狀態(tài)越驻。當(dāng)使用new一個(gè)新線程時(shí)汁政,如new Thread(runnable),但還沒(méi)有執(zhí)行start()缀旁,線程還有沒(méi)有開(kāi)始運(yùn)行记劈,這時(shí)線程的狀態(tài)就是NEW。
  • RUNNABLE:可運(yùn)行線程的線程狀態(tài)并巍。此時(shí)的線程可能正在運(yùn)行目木,也可能沒(méi)有運(yùn)行。
  • BLOCKED:受阻塞并且正在等待監(jiān)視鎖的某一線程的線程狀態(tài)懊渡。
    進(jìn)入阻塞狀態(tài)的情況:
    ① 等待某個(gè)操作的返回刽射,例如IO操作,該操作返回之前剃执,線程不會(huì)繼續(xù)后面的代碼
    ② 等待某個(gè)"鎖"誓禁,在其他線程或程序釋放這個(gè)"鎖"之前,線程不會(huì)繼續(xù)執(zhí)行肾档。
    ③ 等待一定的觸發(fā)條件
    ④ 線程執(zhí)行了sleep()方法
    ⑤ 線程被suspend()方法掛起
    一個(gè)被阻塞的線程在下列情況下會(huì)被重新激活
    ① 執(zhí)行了sleep()摹恰,隨眠時(shí)間已到
    ② 等待的其他線程或者程序持有"鎖"已經(jīng)被釋放
    ③ 正在等待觸發(fā)條件的線程,條件已得到滿(mǎn)足
    ④ 執(zhí)行suspend()方法怒见,被調(diào)用了resume()方法
    ⑤ 等待的操作返回的線程戒祠,操作正確返回。
  • WAITING:某一等待線程的線程狀態(tài)速种。線程因?yàn)檎{(diào)用了Object.wait()或者Thread.join()而未運(yùn)行,就會(huì)進(jìn)入WAITING狀態(tài)低千。
  • TIMED_WAITING:具有指定等待時(shí)間的某一等待線程的線程狀態(tài)配阵。線程是應(yīng)為調(diào)用了Thread.sleep()馏颂,或者加上超時(shí)值在調(diào)用Object.wait()或者Thread.join()而未運(yùn)行,則會(huì)進(jìn)入TIMED_WAITING狀態(tài)棋傍。
  • TERMINATED:已終止線程狀態(tài)救拉。線程已運(yùn)行完畢,它的run()方法已經(jīng)正常結(jié)束或者通過(guò)拋出異常而技術(shù)瘫拣。線程的終止亿絮,run()方法結(jié)束,線程就結(jié)束了麸拄。
5713484-d8bb315210babf51.png
qq_pic_merged_1534782440177.jpg

2.5 線程的啟動(dòng):

代碼在Thread.java 1058行

/**
     * Starts the new Thread of execution. The <code>run()</code> method of
     * the receiver will be called by the receiver Thread itself (and not the
     * Thread calling <code>start()</code>).
     *
     * @throws IllegalThreadStateException - if this thread has already started.
     * @see Thread#run
     */
    public synchronized void start() {
         //保證線程只啟動(dòng)一次
        checkNotStarted();

        hasBeenStarted = true;

        nativeCreate(this, stackSize, daemon);
    }


    private void checkNotStarted() {
        if (hasBeenStarted) {
            throw new IllegalThreadStateException("Thread already started");
        }
    }

start()方法里面首先是判斷是不是啟動(dòng)過(guò)派昧,如果沒(méi)啟動(dòng)過(guò)直接調(diào)用nativeCreate(Thread , long, boolean)方法,通過(guò)方法名拢切,我們知道是一個(gè)nativce方法

代碼在Thread.java 1066行

  private native static void nativeCreate(Thread t, long stackSize, boolean daemon);

2.5.1 nativeCreate()函數(shù)

nativeCreate()這是一個(gè)native方法蒂萎,那么其所對(duì)應(yīng)的JNI方法在哪里?在java_lang_Thread.cc中國(guó)getMethods是一個(gè)JNINativeMethod數(shù)據(jù)
代碼在java_lang_Thread.cc 179行

static JNINativeMethod gMethods[] = {
  NATIVE_METHOD(Thread, currentThread, "!()Ljava/lang/Thread;"),
  NATIVE_METHOD(Thread, interrupted, "!()Z"),
  NATIVE_METHOD(Thread, isInterrupted, "!()Z"),
  NATIVE_METHOD(Thread, nativeCreate, "(Ljava/lang/Thread;JZ)V"),
  NATIVE_METHOD(Thread, nativeGetStatus, "(Z)I"),
  NATIVE_METHOD(Thread, nativeHoldsLock, "(Ljava/lang/Object;)Z"),
  NATIVE_METHOD(Thread, nativeInterrupt, "!()V"),
  NATIVE_METHOD(Thread, nativeSetName, "(Ljava/lang/String;)V"),
  NATIVE_METHOD(Thread, nativeSetPriority, "(I)V"),
  NATIVE_METHOD(Thread, sleep, "!(Ljava/lang/Object;JI)V"),
  NATIVE_METHOD(Thread, yield, "()V"),
};

其中一項(xiàng)為:

NATIVE_METHOD(Thread, nativeCreate, "(Ljava/lang/Thread;JZ)V"),

這里的NATIVE_METHOD定義在java_lang_Object.cc文件中淮椰,如下:
代碼在java_lang_Object.cc 25行

#define NATIVE_METHOD(className, functionName, signature, identifier) \
    { #functionName, signature, reinterpret_cast<void*>(className ## _ ## identifier) }

將宏定義展開(kāi)并帶入五慈,可得所對(duì)應(yīng)的方法名為T(mén)hread_nativeCreate

2.5.2 Thread_nativeCreate()函數(shù)

代碼在java_lang_Thread.cc 49行

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);
}

看到 只是調(diào)用了Thread類(lèi)的CreateNativeThread

2.5.3 Thread::CreateNativeThread()函數(shù)

代碼在thread.cc 388行

void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
  CHECK(java_peer != nullptr);
  Thread* self = static_cast<JNIEnvExt*>(env)->self;
  Runtime* runtime = Runtime::Current();

  // Atomically start the birth of the thread ensuring the runtime isn't shutting down.
  bool thread_start_during_shutdown = false;
  {
    MutexLock mu(self, *Locks::runtime_shutdown_lock_);
    if (runtime->IsShuttingDownLocked()) {
      thread_start_during_shutdown = true;
    } else {
      runtime->StartThreadBirth();
    }
  }
  if (thread_start_during_shutdown) {
    ScopedLocalRef<jclass> error_class(env, env->FindClass("java/lang/InternalError"));
    env->ThrowNew(error_class.get(), "Thread starting during runtime shutdown");
    return;
  }

  Thread* child_thread = new Thread(is_daemon);
  // Use global JNI ref to hold peer live while child thread starts.
  child_thread->tlsPtr_.jpeer = env->NewGlobalRef(java_peer);
  stack_size = FixStackSize(stack_size);

  // Thread.start is synchronized, so we know that nativePeer is 0, and know that we're not racing to
  // assign it.
  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,
                    reinterpret_cast<jlong>(child_thread));

  // Try to allocate a JNIEnvExt for the thread. We do this here as we might be out of memory and
  // do not have a good way to report this on the child's side.
  std::unique_ptr<JNIEnvExt> child_jni_env_ext(
      JNIEnvExt::Create(child_thread, Runtime::Current()->GetJavaVM()));

  int pthread_create_result = 0;
  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);

     /***這里是重點(diǎn),創(chuàng)建線程***/
    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();
  }
  // Manually delete the global reference since Thread::Init will not have been run.
  env->DeleteGlobalRef(child_thread->tlsPtr_.jpeer);
  child_thread->tlsPtr_.jpeer = nullptr;
  delete child_thread;
  child_thread = nullptr;
  // TODO: remove from thread group?
  env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
  {
    std::string msg(child_jni_env_ext.get() == nullptr ?
        "Could not allocate JNI Env" :
        StringPrintf("pthread_create (%s stack) failed: %s",
                                 PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
    ScopedObjectAccess soa(env);
    soa.Self()->ThrowOutOfMemoryError(msg.c_str());
  }
}

這里面重點(diǎn)是pthread_create()函數(shù)主穗,pthread_create是pthread庫(kù)中的函數(shù)泻拦,通過(guò)syscall再調(diào)用到clone來(lái)創(chuàng)建線程。

  • 原型:int pthread_create((pthred_t thread,pthread_attr_t * attr, void * (start_routine) (void * ), void * arg))
  • 頭文件:#include
  • 入?yún)ⅲ?thread(線程標(biāo)識(shí)符)忽媒、attr(線程屬性設(shè)置)争拐、start_routine(線程函數(shù)的起始地址)、arg(傳遞給start_rountine參數(shù)):
  • 返回值:成功則返回0猾浦;失敗則返回-1
  • 功能:創(chuàng)建線程陆错,并調(diào)用線程其實(shí)地址指向函數(shù)start_rountine。

再往下就到內(nèi)核層了金赦,由于篇幅限制音瓷,就先不深入,有興趣的同學(xué)可以自行研究

3 線程的阻塞

線程阻塞指的是暫停一個(gè)線程的執(zhí)行以等待某個(gè)條件發(fā)生(如某資源就緒)夹抗。Java提供了大量的方法來(lái)支持阻塞绳慎,下面讓我們逐一分析。

3.1 sleep()方法

  • sleep()允許指定以毫米為單位的一段時(shí)間作為參數(shù)漠烧,它使得線程在指定的時(shí)間內(nèi)進(jìn)入阻塞狀態(tài)杏愤,不能得到CPU時(shí)間,指定的時(shí)間已過(guò)已脓,線程重新進(jìn)入可執(zhí)行狀態(tài)珊楼。
  • 典型地,sleep()被用在等待某個(gè)資源就緒的情形:測(cè)試發(fā)現(xiàn)條件不滿(mǎn)足后度液,讓線程阻塞一段后重新測(cè)試厕宗,直到條件滿(mǎn)足為止画舌。

3.2 suspend()和resume()方法

  • 兩個(gè)方法配套使用,suspend()使得線程進(jìn)入阻塞狀態(tài)已慢,并且不會(huì)自動(dòng)恢復(fù)曲聂,必須其對(duì)應(yīng)的resume()被調(diào)用,才能使得線程重新進(jìn)入可執(zhí)行狀態(tài)佑惠。
  • 典型地朋腋,suspend()和resume()被用在等待另一個(gè)線程產(chǎn)生的結(jié)果的情形:測(cè)試發(fā)現(xiàn)結(jié)果還沒(méi)有產(chǎn)生后,讓線程阻塞膜楷,另一個(gè)線程產(chǎn)生了結(jié)果后旭咽,調(diào)用resume()使其恢復(fù)。
  • 不建議使用的原因主要有:以suspend()方法為例把将,在調(diào)用后轻专,線程不會(huì)釋放已經(jīng)占有的資源(比如鎖),而是占有著資源進(jìn)入睡眠狀態(tài)察蹲,這樣容易引發(fā)死鎖問(wèn)題

3.3 yield()方法

  • yeield()使得線程放棄當(dāng)前分得的CPU時(shí)間请垛,但是不使線程阻塞,即線程仍處于可執(zhí)行狀態(tài)洽议,隨時(shí)可能再次分的CPU時(shí)間宗收。
  • 調(diào)用yield()效果等價(jià)于調(diào)度程度認(rèn)為該線程已執(zhí)行了足夠的時(shí)間從而轉(zhuǎn)到另一個(gè)線程。

3.4 wait()和notify()方法

  • 兩個(gè)方法配套使用亚兄,wait()使得線程進(jìn)入阻塞狀態(tài)混稽,它有兩種形式,一種允許指定以毫秒為單位的一段時(shí)間作為參數(shù)审胚,另一種沒(méi)有參數(shù)
  • 當(dāng)前對(duì)應(yīng)的notify()被調(diào)用或者超出指定時(shí)間線程重新進(jìn)入可執(zhí)行狀態(tài)匈勋,后者則必須對(duì)應(yīng)的notify()被調(diào)用。
  • 初看起來(lái)它們與suspend()和resume()方法對(duì)沒(méi)有什么分別膳叨,但是事實(shí)上它們是截然不同的洽洁。區(qū)別的核心在于,前面敘述的所有方法菲嘴,阻塞時(shí)都不會(huì)釋放占用的鎖(如果占用的話)饿自,而這一對(duì)方法則相反。
  • 首先龄坪,前面敘述的所有方法都隸屬于Thread類(lèi)昭雌,但是這一對(duì)卻直接隸屬于Object類(lèi)。這一對(duì)方法阻塞時(shí)需要釋放占用的鎖健田,而鎖是任何對(duì)象都具有的烛卧,調(diào)用對(duì)象wait()方法導(dǎo)致線程阻塞,并且該對(duì)象上的鎖釋放妓局。而調(diào)用對(duì)象的notify()方法則導(dǎo)致因調(diào)用對(duì)象的wait()方法而阻塞線程中隨機(jī)選擇的一個(gè)解除阻塞(但要等待獲得鎖后才真正可執(zhí)行)唱星。
  • 其次雳旅,前面敘述的所有方法都可在任何位置調(diào)用,但是這一對(duì)方法卻必須在synchronized方法或塊中調(diào)用间聊,理由也很簡(jiǎn)單,只有synchronized方法或塊中當(dāng)前線程才占有所抵拘,才有鎖可以釋放哎榴。調(diào)用這一對(duì)方法的對(duì)象上的鎖必須為當(dāng)前線程鎖擁有,這樣才有鎖可以釋放僵蛛。因此尚蝌,這一對(duì)方法調(diào)用必須防止在這樣的synchronized方法或者塊中,該方法或者塊的上鎖對(duì)象就是調(diào)用這對(duì)方法的對(duì)象充尉。若不滿(mǎn)足這一條件飘言,則程序雖然仍能編譯,但是在運(yùn)行時(shí)會(huì)出現(xiàn)IllegalMonitorStateException異常驼侠。
  • 第三姿鸿,調(diào)用notify()方法導(dǎo)致解除阻塞的線程是從因調(diào)用該對(duì)象的wait()方法而阻塞的線程中隨機(jī)選取的,我們無(wú)法預(yù)料那個(gè)一個(gè)線程將會(huì)被選擇倒源,所以編程時(shí)需要特別小心苛预,避免因這種不確定性而產(chǎn)生問(wèn)題。
  • 最后笋熬,除了notify()热某,還有一個(gè)方法notifyAll()也可能其到類(lèi)似作用,唯一的區(qū)別是在于胳螟,調(diào)用notifyAll()方法將把 因 調(diào)用該對(duì)象的wait()方法而阻塞的所有線程一次性全部解除阻塞昔馋。當(dāng)然,只有獲得鎖的那一個(gè)線程才能進(jìn)入可執(zhí)行狀態(tài)糖耸。

4 關(guān)于線程上下文切換

在多線程編程中秘遏,多個(gè)線程公用資源,計(jì)算機(jī)會(huì)多各個(gè)線程進(jìn)行調(diào)度蔬捷。因此各個(gè)線程會(huì)經(jīng)歷一系列不同的狀態(tài)垄提,以及在不同的線程間進(jìn)行切換。
既然線程需要被切換周拐,在生命周期中處于各種狀態(tài)铡俐,如等待、阻塞妥粟、運(yùn)行审丘。吧線程就需要能夠保存線程,在線程被切換后/回復(fù)后需要繼續(xù)在上一個(gè)狀態(tài)運(yùn)行勾给。這就是所謂的上下文切換滩报。為了實(shí)現(xiàn)上下文切換锅知,勢(shì)必會(huì)消耗資源,造成性能損失脓钾。因?yàn)槲覀冊(cè)谶M(jìn)行多線程編程過(guò)程中需要減少上下文切換售睹,提高程序運(yùn)行性能。
java多線程(一)---上下文切換

5 關(guān)于線程的安全問(wèn)題

線程安全無(wú)非是要控制多個(gè)線程對(duì)某個(gè)資源的有序訪問(wèn)或修改可训。即多個(gè)線程昌妹,一個(gè)臨界區(qū),通過(guò)通知這些線程對(duì)臨界區(qū)的訪問(wèn)握截,使得每個(gè)線程的每次執(zhí)行結(jié)果都相同

5.1 實(shí)現(xiàn)線程安全的工具:

  • 1 隱式鎖:synchronized
  • 2 顯式鎖:java.util.concurrent.lock
  • 3 關(guān)鍵字:volatile
  • 4 原子操作:

6 停止線程

Java 停止線程

7 線程中的優(yōu)先級(jí)

  • 線程可以劃分優(yōu)先級(jí)飞崖,優(yōu)先級(jí)較高的線程得到的CPU資源較多,也就是CPU優(yōu)先執(zhí)行優(yōu)先級(jí)較高的線程對(duì)象中的任務(wù)谨胞。

設(shè)置線程的優(yōu)先級(jí)使用setPriority()方法固歪,默認(rèn)優(yōu)先級(jí)是5。
線程的優(yōu)先級(jí)分為1~10這10個(gè)等級(jí)胯努,如果小于1或大于10牢裳,則JDK拋出異常throw new IllegalArgumentException()。
JDK中使用3個(gè)常量來(lái)預(yù)置定義優(yōu)先級(jí)的值康聂,代碼如下:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

  • 優(yōu)先級(jí)高的線程分配時(shí)間片的數(shù)量要多于優(yōu)先級(jí)低的線程贰健。
    • 設(shè)置線程優(yōu)先級(jí)時(shí),針對(duì)頻繁阻塞(休眠或者I/O操作)的線程需要設(shè)置較高優(yōu)先級(jí)
    • 偏重計(jì)算(需要較多CPU時(shí)間或者偏運(yùn)算)的線程則設(shè)置較低的優(yōu)先級(jí)恬汁,確保處理器不會(huì)被獨(dú)占伶椿。

在不同的JVM以及操作系統(tǒng)上,線程規(guī)劃會(huì)存在差異氓侧,有些操作系統(tǒng)甚至?xí)雎詫?duì)線程優(yōu)先級(jí)的設(shè)定

繼承性
在Java中脊另,線程的優(yōu)先級(jí)具有繼承性,比如A線程啟動(dòng)B線程约巷,則B線程的優(yōu)先級(jí)與A是一樣的偎痛。
優(yōu)先級(jí)被更改再繼續(xù)繼承
規(guī)則性
高優(yōu)先級(jí)的線程總是大部分先執(zhí)行完,但不代表高優(yōu)先級(jí)的線程全部先執(zhí)行完独郎。
當(dāng)線程優(yōu)先級(jí)的等級(jí)差距很大時(shí)踩麦,誰(shuí)先執(zhí)行完和代碼的調(diào)用順序無(wú)關(guān)。
隨機(jī)性
線程的優(yōu)先級(jí)還具有“隨機(jī)性”氓癌,也就是優(yōu)先級(jí)較高的線程不一定每一次都先執(zhí)行完谓谦。
不要把線程的優(yōu)先級(jí)與運(yùn)行結(jié)果的順序作為衡量的標(biāo)準(zhǔn),優(yōu)先級(jí)較高的線程并不一定每一次都先執(zhí)行完run()方法中的任務(wù)贪婉,也就是說(shuō)反粥,線程優(yōu)先級(jí)與打印順序無(wú)關(guān),不要將這兩者的關(guān)系相關(guān)聯(lián),它們的關(guān)系具有不確定性和隨機(jī)性

8 守護(hù)線程

8.1 概念

守護(hù)線程我覺(jué)得還是很有用的才顿。首先看看守護(hù)進(jìn)程是什么?守護(hù)線程就是后臺(tái)運(yùn)行的線程莫湘。普通線程結(jié)束后,守護(hù)線程自動(dòng)結(jié)束郑气。一般main線程視為守護(hù)線程幅垮,以及GC、數(shù)據(jù)庫(kù)連接池等竣贪,都做成守護(hù)線程军洼。

8.2 特點(diǎn)

守護(hù)線程就像備胎一樣,JRE(女神)根本不會(huì)管守護(hù)進(jìn)行有沒(méi)有演怎,在不在,只要前臺(tái)線程結(jié)束避乏,就算執(zhí)行完畢了爷耀。

8.3 如何使用

直接調(diào)用setDeamon() 即可。

8.4 注意事項(xiàng)

setDaemon(true) 必須在start()方法之前調(diào)用鲤孵;在守護(hù)線程中開(kāi)啟的線程也是守護(hù)線程贺喝;守護(hù)線程中不能進(jìn)行I/O操作包颁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咆耿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌爹橱,老刑警劉巖萨螺,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異愧驱,居然都是意外死亡慰技,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)组砚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)吻商,“玉大人,你說(shuō)我怎么就攤上這事糟红“剩” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵盆偿,是天一觀的道長(zhǎng)柒爸。 經(jīng)常有香客問(wèn)我,道長(zhǎng)陈肛,這世上最難降的妖魔是什么揍鸟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上阳藻,老公的妹妹穿的比我還像新娘晰奖。我一直安慰自己,他們只是感情好腥泥,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布匾南。 她就那樣靜靜地躺著,像睡著了一般蛔外。 火紅的嫁衣襯著肌膚如雪蛆楞。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天夹厌,我揣著相機(jī)與錄音豹爹,去河邊找鬼。 笑死矛纹,一個(gè)胖子當(dāng)著我的面吹牛臂聋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播或南,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼孩等,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了采够?” 一聲冷哼從身側(cè)響起肄方,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹬癌,沒(méi)想到半個(gè)月后权她,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冀瓦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年伴奥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翼闽。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拾徙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出感局,到底是詐尸還是另有隱情尼啡,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布询微,位于F島的核電站崖瞭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏撑毛。R本人自食惡果不足惜书聚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雌续,春花似錦斩个、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鸽心,卻和暖如春滚局,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背顽频。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工藤肢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糯景。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓谤草,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親莺奸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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