您好,登錄后才能下訂單哦!
什么是線程?
線程(英語(yǔ):thread)是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。在Unix System V及SunOS中也被稱為輕量進(jìn)程(lightweight processes),但輕量進(jìn)程更多指內(nèi)核線程(kernel thread),而把用戶線程(user thread)稱為線程。
線程是獨(dú)立調(diào)度和分派的基本單位。線程可以為操作系統(tǒng)內(nèi)核調(diào)度的內(nèi)核線程,如Win32線程;由用戶進(jìn)程自行調(diào)度的用戶線程,如Linux平臺(tái)的POSIX Thread;或者由內(nèi)核與用戶進(jìn)程,如Windows 7的線程,進(jìn)行混合調(diào)度。
我們知道,一個(gè)線程可以用來(lái)執(zhí)行一個(gè)任務(wù),并且該任務(wù)的執(zhí)行是異步的,并不會(huì)阻塞后面的代碼。在一個(gè)java進(jìn)程中,包含main方法的類也是在一個(gè)線程中執(zhí)行的。在實(shí)際應(yīng)用中,如果需要處理一個(gè)比較耗時(shí)的操作,為了不影響程序整體的響應(yīng),通常會(huì)將這個(gè)耗時(shí)的操作封裝到一個(gè)線程中,異步的執(zhí)行。但是,線程是怎樣實(shí)現(xiàn)任務(wù)的異步執(zhí)行的呢?本文將深入了解Thread類,以期望得出線程執(zhí)行的秘密。
根據(jù)《深入理解JAVA虛擬機(jī)》中關(guān)于線程的章節(jié),我們得知,在java中一個(gè)Thread對(duì)應(yīng)著操作系統(tǒng)中的一個(gè)線程。而操作系統(tǒng)的線程是稀缺資源,不能無(wú)限制的創(chuàng)建線程,這也就是為什么要使用線程池的原因之一。
我們也知道,在java中要實(shí)現(xiàn)一個(gè)線程,有兩種方式:
但是不管是哪種方式,最后線程的執(zhí)行還是要通過(guò)調(diào)用Thread的start()方法
讓我們看一下Thread類的重要屬性和方法:
// target就是一個(gè)傳遞給Thread等待Thread執(zhí)行的Runnable對(duì)象
/* What will be run. */
private Runnable target;
/* The group of this thread */
private ThreadGroup group;
// 類方法,該方法會(huì)在Thread類初始化時(shí),在類的構(gòu)造器<clinit>中被調(diào)用,且只會(huì)調(diào)用一次,該方法主要的作用是注冊(cè)一些本地的方法,以便后期可以使用
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
// 注冊(cè)了以下本地方法:
public static native Thread currentThread();
public static nativevoid yield();
public static native void sleep(long millis) throws InterruptedException;
private native void start0();
private native boolean isInterrupted(boolean ClearInterrupted);
public final native boolean isAlive();
public static native boolean holdsLock(Object obj);
private native static StackTraceElement[][] dumpThreads(Thread[] threads);
private native static Thread[] getThreads();
private native void setPriority0(int newPriority);
private native void stop0(Object o);
private native void suspend0();
private native void resume0();
private native void interrupt0();
private native void setNativeName(String name);
讓我們看看下面這段代碼將輸出什么內(nèi)容:
public static class MyThread extends Thread{
@Override
public void run(){
System.out.println("MyThread---1");
}
}
public static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable---1");
}
}
public static void main(String[] args) {
Thread t1 = new MyThread();
Thread t2 = new Thread(new MyRunnable());
t1.start();
t2.start();
System.out.println("MyThread---2");
System.out.println("MyRunnable---2");
}
該代碼的輸出內(nèi)容是不確定的,可能輸出為:
MyThread---2
MyRunnable---2
MyRunnable---1
MyThread---1
也可能輸出為:
MyThread---1
MyRunnable---1
MyThread---2
MyRunnable---2
但是如果把上述的代碼t1.start(),t2.start()改為:
t1.run();
t2.run();
那么輸出將變成確定的:
MyThread---1
MyRunnable---1
MyThread---2
MyRunnable---2
為什么使用start(),輸出的內(nèi)容是不確定的,而使用run()輸出卻是確定的呢?這就需要從Thread的啟動(dòng)過(guò)程開始了解了。 Thread類中start()方法的源代碼如下:
public synchronized void start() {
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. */
group.add(this);
boolean started = false;
try {
start0();
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 */
}
}
}
private native void start0();
可以看到,start()方法內(nèi)部其實(shí)調(diào)用了一個(gè)native的方法start0()。而在Thread類初始化時(shí),執(zhí)行了一個(gè)registerNatives()的方法來(lái)注冊(cè)本地方法,其中start0方法實(shí)際關(guān)聯(lián)的是JVM_StartThread方法:
{"start0", "()V",(void *)&JVM_StartThread}
在 jvm.cpp 中,有如下代碼段:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)){
...
native_thread = new JavaThread(&thread_entry, sz);
...
}
這里JVMENTRY是一個(gè)宏,用來(lái)定義JVMStartThread函數(shù),可以看到函數(shù)內(nèi)創(chuàng)建了真正的平臺(tái)相關(guān)的本地線程,其線程函數(shù)是thread_entry,如下:
static void thread_entry(JavaThread* thread, TRAPS) {
HandleMark hm(THREAD);
Handle obj(THREAD, thread->threadObj());
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,obj,KlassHandle(THREAD,SystemDictionary::Thread_klass()),
vmSymbolHandles::run_method_name(), //調(diào)用了run_method_name
vmSymbolHandles::void_method_signature(),THREAD);
}
可以看到調(diào)用了vmSymbolHandles::runmethodname方法,而runmethodname是在vmSymbols.hpp 用宏定義的:
class vmSymbolHandles: AllStatic {
...
// 這里決定了調(diào)用的方法名稱是 “run”
template(run_method_name,"run")
...
}
從以上的代碼中可以看出,Thread執(zhí)行start()方法,首先會(huì)創(chuàng)建一個(gè)新的操作系統(tǒng)的線程,然后當(dāng)該操作線程得到CPU時(shí)間片時(shí),會(huì)執(zhí)行一個(gè)回調(diào)方法:run(),這也就證明了通過(guò)start()可以創(chuàng)建一個(gè)新線程并異步執(zhí)行線程體,而通過(guò)run()只是執(zhí)行了一個(gè)Thread對(duì)象的普通方法而已,并不會(huì)并行執(zhí)行,而是串行執(zhí)行的。
以下附上一些Thread相關(guān)的常見性的問(wèn)題:
public static void main(String[] args){
Thread t1 = new Thread();
t1.start();
t1.join();
// 以下代碼會(huì)在t1執(zhí)行完畢后打印
System.out.println("t1 finished");
}
wait()和notify()必須操作同一個(gè)"對(duì)象監(jiān)視器"
Runnable1 implements Runnable{
public void run(){
synchronized(lock){
// 等待其他線程來(lái)喚醒
lock.wait();
System.out.println("Runnable1 has been notified by other thread");
}
}
}
Runnable2 implements Runnable{
public void run(){
synchronized(lock){
System.out.println("Runnable2 will notify other thread who wait for lock");
// 喚醒其他線程 lock.notify();
}
}
}
public static void main(String[] args){
Object lock = new Object();
Thread t1 = new Thread(new Runnable1(lock));
Thread t2 = new Thread(new Runnable2(lock));
t1.start();
t2.start();
}
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
// t1/t2線程操作的都是同一個(gè)實(shí)例r,所以r中的數(shù)據(jù)可以實(shí)現(xiàn)多線程共享
t1.start();
t2.start();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
Object result = futureTask.get();
1) CountDownLatch和CyclicBarrier都能夠?qū)崿F(xiàn)線程之間的等待,只不過(guò)它們側(cè)重點(diǎn)不同:
2) CountDownLatch一般用于某個(gè)線程A等待若干個(gè)其他線程執(zhí)行完任務(wù)之后,它才執(zhí)行;
3) CyclicBarrier一般用于一組線程互相等待至某個(gè)狀態(tài),然后這一組線程再同時(shí)執(zhí)行;
4) 另外,CountDownLatch是不能夠重用的,而CyclicBarrier是可以重用的。
5) Semaphore其實(shí)和鎖有點(diǎn)類似,它一般用于控制對(duì)某組資源的訪問(wèn)權(quán)限。
線程池有兩個(gè)參數(shù):核心線程數(shù)coreNum和最大線程數(shù)maxNum
假設(shè)初始化一個(gè)線程池,核心線程數(shù)是5,最大線程數(shù)是10,線程池初始化的時(shí)候,里面是沒(méi)有線程的
當(dāng)來(lái)了一個(gè)任務(wù)時(shí),就初始化了一個(gè)線程,如果再來(lái)一個(gè)任務(wù),再初始化了一個(gè)線程,連續(xù)初始化了5個(gè)線程之后,如果第6個(gè)任務(wù)過(guò)來(lái)了
這時(shí)會(huì)把第6個(gè)任務(wù)放到阻塞隊(duì)列中
現(xiàn)在線程池中有了5個(gè)線程,如果其中一個(gè)線程空閑了,就會(huì)從阻塞隊(duì)列中獲取第6個(gè)任務(wù),進(jìn)行執(zhí)行
如果線程池的5個(gè)線程都在running狀態(tài),那么任務(wù)就先保存在阻塞隊(duì)列中
如果隊(duì)列滿了,并且我們?cè)O(shè)置了最大線程數(shù)是10,但線程池中只有5個(gè)線程,這時(shí)會(huì)新建一個(gè)線程去執(zhí)行不能保存到阻塞隊(duì)列的任務(wù),此時(shí)線程池中有了6個(gè)線程
如果線程池中的線程數(shù)達(dá)到10個(gè)了,并且阻塞隊(duì)列也滿了,則可以通過(guò)自定義的reject函數(shù)去處理這些任務(wù)
最后運(yùn)行一段時(shí)間之后,阻塞隊(duì)列中的任務(wù)也執(zhí)行完了,線程池中超過(guò)核心線程數(shù)的線程會(huì)在空閑一段時(shí)間內(nèi)自動(dòng)回收
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。