進程和多線程的概念

1 線程概念

1.1 進程

  • 在現(xiàn)代的操作系統(tǒng)中,進程是資源分配的最小單位,而線程是CPU調度的基本單位。
  • 一個進程中最少有一個線程,名叫主線程。進程是程序執(zhí)行的一個實例,比如說10個用戶同時執(zhí)行Chrome瀏覽器,那么就有10個獨立的進程(盡管他們共享一個可執(zhí)行代碼)。

1.1.1 進程的特點:

  • 每一個進程都有自己的獨立的一塊內存空間、一組資源系統(tǒng)。其內部數(shù)據(jù)和狀態(tài)都是完全獨立的。
  • 進程的優(yōu)點是提高CPU的運行效率,在同一個時間內執(zhí)行多個程序,即并發(fā)執(zhí)行。但是從嚴格上將,也不是絕對的同一時刻執(zhí)行多個程序,只不過CPU在執(zhí)行時通過時間片等調度算法不同進程告訴切換。

所以總結來說:進程由操作系統(tǒng)調度,簡單而且穩(wěn)定,進程之間的隔離性好,一個進程的奔潰不會影響其他進程。單進程編程簡單,在多個情況下可以把進程和CPU進行綁定,從分利用CPU。

1.1.2 進程的缺點:

  • 一般來說進程消耗的內存比較大,進程切換代價很高,進程切換也像線程一樣需要保持上一個進程的上下文環(huán)境。
  • 比如在Web編程中,如果一個進程處理一個請求的話,如果提高并發(fā)量就要提高進程數(shù),而進程數(shù)量受內存和切換代價限制。

1.2 線程

  • 線程是進程的一個實體,是CPU調度和分配的基本單位,它比進程更下能獨立運行的基本單位,線程自己基本上不擁有系統(tǒng)資源,只擁有一點在運行中不可少的資源(如程序計數(shù)器、棧),但是它可與同屬一個進程的其他線程共享進程所擁有的全部資源。
  • 同類的多線程共享一塊內存空間個一組系統(tǒng)資源,線程本身的數(shù)據(jù)通常只有CPU的寄存器數(shù)據(jù),以及一個供程序執(zhí)行的堆棧。
  • 線程在切換時負荷小,因此,線程也稱為輕負荷進程。一個進程中可以包含多個線程。
  • 在JVM中,本地方法棧、虛擬機棧和程序計數(shù)器是線程隔離的,而堆區(qū)和方法區(qū)是線程共享的。

1.3 進程線程的區(qū)別

  • 地址空間:線程是進程內的一個執(zhí)行單元;進程至少有一個線程;一個進程內的多線程它們共享進程的地址空間;而進程自己獨立的地址空間
  • 資源擁有:進程是資源分配和擁有的單位,同一個進程內的線程共享進程的資源。
  • 線程是處理器調度的基本單位,但進程不是
  • 二者均可并發(fā)執(zhí)行
  • 并發(fā):多個事件在同一個時間段內一起執(zhí)行
  • 并行:多個事件在同一時刻同時執(zhí)行

1.4 多線程

  • 雖然多線程進一步提高了應用的執(zhí)行效率,但是由于線程之間會共享內存資源,這會導致一些資源同步的問題,另外,線程之間切換也會對資源有所消耗。

  • 如果一臺電腦只有一個CPU核心,那么多線也并沒有真正的"同時"運行,它們之間需要通過相互切換來共享CPU核心,所以,只有一個CPU核心的情況下,多線程不會提高應用效率。

  • 但是,現(xiàn)在計算機一般都會有多個CPU,并且每個CPU可能還有會多個核心,所以現(xiàn)在硬件資源條件下,多線程編程可以極大的提高應用的效率。


    5713484-0f7a599138b3862f.png
  • 在Java程序中,JVM負責線程的調度。線程調度是按照特定的機制為多個線程分配CPU的使用權。

  • 調度的模式有兩種:分時調度搶占式調度。分時調度是所有線程輪流獲得CPU使用權,并平均分配每個線程占用CPU的時間;搶占式調度是根據(jù)線程的優(yōu)先級別來獲取CPU的使用權。JVM的線程調度模式采用了搶占式模式。

2 Thread類

Thread.java

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

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

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

Runnable是一個接口類,唯一提供的方法就是run()。

2.1 Thread的使用

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

異步和同步

  • Thread.java類中start()方法通知“線程規(guī)劃器”此線程已經準備就緒,等待調用線程對象的run()方法。這個過程就是讓系統(tǒng)安排一個時間來調用Thread中的run()方法,也就是使線程得到運行,啟動線程,具有異步執(zhí)行的效果。執(zhí)行start()方法的順序不代表線程啟動的順序
  • 如果調用代碼thread.run()就不是異步執(zhí)行了,而是同步,那么此線程對象并不交給“線程規(guī)劃器”來進行處理,而是由main主線程來調用run()方法,也就是必須等run方法中的代碼執(zhí)行完才可以執(zhí)行后面的代碼。
  • Thread類實現(xiàn)了Runnable接口--構造方法不僅可以傳入Runnable接口的對象還可以傳入一個Thread類的對象,這樣就可以將一個thread對象中的run()方法交由其他的線程進行調用。

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:每一個線程都屬于一個group,當線程被創(chuàng)建是,就會加入一個特定的group,當前程運行結束,會從這個group移除。
  • deamon:當前線程是不是守護線程,守護線程只會在沒有非守護線程運行下才會運行
  • name:線程名稱
  • priority:線程優(yōu)先級,Thread的線程優(yōu)先級取值范圍為[1,10],默認優(yōu)先級為5
  • stackSize:線程棧大小,默認是0,即使用默認的線程棧大小(由dalvik中的全局變量gDvm.stackSize決定)
  • target:一個Runnable對象,Thread的run()方法中會轉調target的run()方法,這是線程真正處理事務的地方。
  • id:線程id,通過遞增的count得到該id,如果沒有顯式給線程設置名字,那么久會使用Thread+id當前線程的名字。注意這里不是真正的線程id,即在logcat中打印的tid并不是這個id,那tid是指dalvik線程id
  • localValues:本地線程存儲(TLS)數(shù)據(jù) 關于TLS后面會詳細介紹

2.4 create()方法:

為什么要研究create()方法?因為Thread一種有9個構造函數(shù),其中8個里面最終都是調用了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內部代碼大體上分為3個部分

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

2.4 Thread的生命周期:

線程共有6種狀態(tài);在某一時刻只能是這6種狀態(tài)之一。這些狀態(tài)由Thread.State這個枚舉類型表示,并且可以通過getState()方法獲得當前具體的狀態(tà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 : 起勁尚未啟動的線程的狀態(tài)。當使用new一個新線程時,如new Thread(runnable),但還沒有執(zhí)行start(),線程還有沒有開始運行,這時線程的狀態(tài)就是NEW。
  • RUNNABLE:可運行線程的線程狀態(tài)。此時的線程可能正在運行,也可能沒有運行。
  • BLOCKED:受阻塞并且正在等待監(jiān)視鎖的某一線程的線程狀態(tài)。
    進入阻塞狀態(tài)的情況:
    ① 等待某個操作的返回,例如IO操作,該操作返回之前,線程不會繼續(xù)后面的代碼
    ② 等待某個"鎖",在其他線程或程序釋放這個"鎖"之前,線程不會繼續(xù)執(zhí)行。
    ③ 等待一定的觸發(fā)條件
    ④ 線程執(zhí)行了sleep()方法
    ⑤ 線程被suspend()方法掛起
    一個被阻塞的線程在下列情況下會被重新激活
    ① 執(zhí)行了sleep(),隨眠時間已到
    ② 等待的其他線程或者程序持有"鎖"已經被釋放
    ③ 正在等待觸發(fā)條件的線程,條件已得到滿足
    ④ 執(zhí)行suspend()方法,被調用了resume()方法
    ⑤ 等待的操作返回的線程,操作正確返回。
  • WAITING:某一等待線程的線程狀態(tài)。線程因為調用了Object.wait()或者Thread.join()而未運行,就會進入WAITING狀態(tài)。
  • TIMED_WAITING:具有指定等待時間的某一等待線程的線程狀態(tài)。線程是應為調用了Thread.sleep(),或者加上超時值在調用Object.wait()或者Thread.join()而未運行,則會進入TIMED_WAITING狀態(tài)。
  • TERMINATED:已終止線程狀態(tài)。線程已運行完畢,它的run()方法已經正常結束或者通過拋出異常而技術。線程的終止,run()方法結束,線程就結束了。
5713484-d8bb315210babf51.png
qq_pic_merged_1534782440177.jpg

2.5 線程的啟動:

代碼在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() {
         //保證線程只啟動一次
        checkNotStarted();

        hasBeenStarted = true;

        nativeCreate(this, stackSize, daemon);
    }


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

start()方法里面首先是判斷是不是啟動過,如果沒啟動過直接調用nativeCreate(Thread , long, boolean)方法,通過方法名,我們知道是一個nativce方法

代碼在Thread.java 1066行

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

2.5.1 nativeCreate()函數(shù)

nativeCreate()這是一個native方法,那么其所對應的JNI方法在哪里?在java_lang_Thread.cc中國getMethods是一個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"),
};

其中一項為:

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

將宏定義展開并帶入,可得所對應的方法名為Thread_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);
}

看到 只是調用了Thread類的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);

     /***這里是重點,創(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());
  }
}

這里面重點是pthread_create()函數(shù),pthread_create是pthread庫中的函數(shù),通過syscall再調用到clone來創(chuàng)建線程。

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

再往下就到內核層了,由于篇幅限制,就先不深入,有興趣的同學可以自行研究

3 線程的阻塞

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

3.1 sleep()方法

  • sleep()允許指定以毫米為單位的一段時間作為參數(shù),它使得線程在指定的時間內進入阻塞狀態(tài),不能得到CPU時間,指定的時間已過,線程重新進入可執(zhí)行狀態(tài)。
  • 典型地,sleep()被用在等待某個資源就緒的情形:測試發(fā)現(xiàn)條件不滿足后,讓線程阻塞一段后重新測試,直到條件滿足為止。

3.2 suspend()和resume()方法

  • 兩個方法配套使用,suspend()使得線程進入阻塞狀態(tài),并且不會自動恢復,必須其對應的resume()被調用,才能使得線程重新進入可執(zhí)行狀態(tài)。
  • 典型地,suspend()和resume()被用在等待另一個線程產生的結果的情形:測試發(fā)現(xiàn)結果還沒有產生后,讓線程阻塞,另一個線程產生了結果后,調用resume()使其恢復。
  • 不建議使用的原因主要有:以suspend()方法為例,在調用后,線程不會釋放已經占有的資源(比如鎖),而是占有著資源進入睡眠狀態(tài),這樣容易引發(fā)死鎖問題

3.3 yield()方法

  • yeield()使得線程放棄當前分得的CPU時間,但是不使線程阻塞,即線程仍處于可執(zhí)行狀態(tài),隨時可能再次分的CPU時間。
  • 調用yield()效果等價于調度程度認為該線程已執(zhí)行了足夠的時間從而轉到另一個線程。

3.4 wait()和notify()方法

  • 兩個方法配套使用,wait()使得線程進入阻塞狀態(tài),它有兩種形式,一種允許指定以毫秒為單位的一段時間作為參數(shù),另一種沒有參數(shù)
  • 當前對應的notify()被調用或者超出指定時間線程重新進入可執(zhí)行狀態(tài),后者則必須對應的notify()被調用。
  • 初看起來它們與suspend()和resume()方法對沒有什么分別,但是事實上它們是截然不同的。區(qū)別的核心在于,前面敘述的所有方法,阻塞時都不會釋放占用的鎖(如果占用的話),而這一對方法則相反。
  • 首先,前面敘述的所有方法都隸屬于Thread類,但是這一對卻直接隸屬于Object類。這一對方法阻塞時需要釋放占用的鎖,而鎖是任何對象都具有的,調用對象wait()方法導致線程阻塞,并且該對象上的鎖釋放。而調用對象的notify()方法則導致因調用對象的wait()方法而阻塞線程中隨機選擇的一個解除阻塞(但要等待獲得鎖后才真正可執(zhí)行)。
  • 其次,前面敘述的所有方法都可在任何位置調用,但是這一對方法卻必須在synchronized方法或塊中調用,理由也很簡單,只有synchronized方法或塊中當前線程才占有所,才有鎖可以釋放。調用這一對方法的對象上的鎖必須為當前線程鎖擁有,這樣才有鎖可以釋放。因此,這一對方法調用必須防止在這樣的synchronized方法或者塊中,該方法或者塊的上鎖對象就是調用這對方法的對象。若不滿足這一條件,則程序雖然仍能編譯,但是在運行時會出現(xiàn)IllegalMonitorStateException異常。
  • 第三,調用notify()方法導致解除阻塞的線程是從因調用該對象的wait()方法而阻塞的線程中隨機選取的,我們無法預料那個一個線程將會被選擇,所以編程時需要特別小心,避免因這種不確定性而產生問題。
  • 最后,除了notify(),還有一個方法notifyAll()也可能其到類似作用,唯一的區(qū)別是在于,調用notifyAll()方法將把 因 調用該對象的wait()方法而阻塞的所有線程一次性全部解除阻塞。當然,只有獲得鎖的那一個線程才能進入可執(zhí)行狀態(tài)。

4 關于線程上下文切換

在多線程編程中,多個線程公用資源,計算機會多各個線程進行調度。因此各個線程會經歷一系列不同的狀態(tài),以及在不同的線程間進行切換。
既然線程需要被切換,在生命周期中處于各種狀態(tài),如等待、阻塞、運行。吧線程就需要能夠保存線程,在線程被切換后/回復后需要繼續(xù)在上一個狀態(tài)運行。這就是所謂的上下文切換。為了實現(xiàn)上下文切換,勢必會消耗資源,造成性能損失。因為我們在進行多線程編程過程中需要減少上下文切換,提高程序運行性能。
java多線程(一)---上下文切換

5 關于線程的安全問題

線程安全無非是要控制多個線程對某個資源的有序訪問或修改。即多個線程,一個臨界區(qū),通過通知這些線程對臨界區(qū)的訪問,使得每個線程的每次執(zhí)行結果都相同

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

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

6 停止線程

Java 停止線程

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

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

設置線程的優(yōu)先級使用setPriority()方法,默認優(yōu)先級是5。
線程的優(yōu)先級分為1~10這10個等級,如果小于1或大于10,則JDK拋出異常throw new IllegalArgumentException()。
JDK中使用3個常量來預置定義優(yōu)先級的值,代碼如下:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

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

在不同的JVM以及操作系統(tǒng)上,線程規(guī)劃會存在差異,有些操作系統(tǒng)甚至會忽略對線程優(yōu)先級的設定

繼承性
在Java中,線程的優(yōu)先級具有繼承性,比如A線程啟動B線程,則B線程的優(yōu)先級與A是一樣的。
優(yōu)先級被更改再繼續(xù)繼承
規(guī)則性
高優(yōu)先級的線程總是大部分先執(zhí)行完,但不代表高優(yōu)先級的線程全部先執(zhí)行完。
當線程優(yōu)先級的等級差距很大時,誰先執(zhí)行完和代碼的調用順序無關。
隨機性
線程的優(yōu)先級還具有“隨機性”,也就是優(yōu)先級較高的線程不一定每一次都先執(zhí)行完。
不要把線程的優(yōu)先級與運行結果的順序作為衡量的標準,優(yōu)先級較高的線程并不一定每一次都先執(zhí)行完run()方法中的任務,也就是說,線程優(yōu)先級與打印順序無關,不要將這兩者的關系相關聯(lián),它們的關系具有不確定性和隨機性

8 守護線程

8.1 概念

守護線程我覺得還是很有用的。首先看看守護進程是什么?守護線程就是后臺運行的線程。普通線程結束后,守護線程自動結束。一般main線程視為守護線程,以及GC、數(shù)據(jù)庫連接池等,都做成守護線程。

8.2 特點

守護線程就像備胎一樣,JRE(女神)根本不會管守護進行有沒有,在不在,只要前臺線程結束,就算執(zhí)行完畢了。

8.3 如何使用

直接調用setDeamon() 即可。

8.4 注意事項

setDaemon(true) 必須在start()方法之前調用;在守護線程中開啟的線程也是守護線程;守護線程中不能進行I/O操作。

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容