繼承Thread類實現多線程
java.lang.Thread
是一個線程操作的核心類。新建一個線程簡單的方法就是直接繼承Thread類,而後覆寫該類中的 run()方法(就相當於主類中的main方法)
package www.bit.java;
class MyThread extends Thread {
private int ticket = 100;
@Override
public void run() {
while(this.ticket > 0)
System.out.println(Thread.currentThread().getName()+"還剩下"+this.ticket--+"票");
}
}
public class TestThread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
MyThread myThread1 = new MyThread();
myThread.start();
myThread1.start();
}
}
爲什麼要通過start()方法來調用run()方法,而不是run()直接執行?
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* <code>start</code> method) and the other thread (which executes its
* <code>run</code> method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
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".
*/
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()方法中拋出
IllegalThreadStateException
異常,按照原有的處理方式,應當在調用處進行異常處理,而此處沒有處理也不會報錯,因此是一個RunTimeException,這個異常的產生只是因爲你重複啓動了線程纔會產生。所以,每一個線程對象只能夠啓動一次。 - 下面我們看到了在start()方法中調用了start0()方法,而這個方法是一個只聲明而未實現的方法同時使用native關鍵字進行定義。
- native指的是調用本機的原生系統函數。
- Thread 類有個 registerNatives 本地方法,該方法主要的作用就是註冊一些本地方法供 Thread 類使用,如 start0(), stop0() 等等,可以說,所有操作本地線程的本地方法都是由它註冊的。
這個方法放在一個 static 語句塊中,當該類被加載到 JVM 中的時候,它就會被調用,進而註冊相應的本地方法。
private static native void registerNatives();
static {
registerNatives();
}
而本地方法 registerNatives
是定義在 Thread.c 文件中的。Thread.c 是個很小的文件,它定義了各個操作系統平臺都要用到的關於線程的公用數據和操作,
JNIEXPORT void JNICALL Java_Java_lang_Thread_registerNatives (JNIEnv \*env, jclass cls){ //registerNatives
(\*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));
}
static JNINativeMethod methods[] = {
{"start0", "()V",(void \*)&JVM_StartThread}, //start0 方法
{"stop0", "(" OBJ ")V", (void \*)&JVM_StopThread},
{"isAlive","()Z",(void \*)&JVM_IsThreadAlive},
{"suspend0","()V",(void \*)&JVM_SuspendThread},
{"resume0","()V",(void \*)&JVM_ResumeThread},
{"setPriority0","(I)V",(void \*)&JVM_SetThreadPriority},
{"yield", "()V",(void \*)&JVM_Yield},
{"sleep","(J)V",(void \*)&JVM_Sleep},
{"currentThread","()" THD,(void \*)&JVM_CurrentThread},
{"countStackFrames","()I",(void \*)&JVM_CountStackFrames},
{"interrupt0","()V",(void \*)&JVM_Interrupt},
{"isInterrupted","(Z)Z",(void \*)&JVM_IsInterrupted},
{"holdsLock","(" OBJ ")Z",(void \*)&JVM_HoldsLock},
{"getThreads","()[" THD,(void \*)&JVM_GetAllThreads},
{"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads},
};
觀察上邊一小段代碼,可以容易的看出 Java 線程調用 start->start0
的方法,實際上會調用到 JVM_StartThread
方法,那這個方法又是怎麼處理的呢?
實際上,我們需要看到的是該方法終要調用 Java 線程的 run 方法,事實的確也是這樣的。
在 jvm.cpp 中,有如下代碼段:
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)){
...
native_thread = new JavaThread(&thread_entry, sz);
...
}
這裏JVM_ENTRY
是一個宏,用來定義JVM_StartThread
函數,可以看到函數內創建了真正的平臺相關的本地線程, 其線程函數是 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(), //LOOK! 看這裏
vmSymbolHandles::void_method_signature(),THREAD);
}
可以看到調用了 vmSymbolHandles::run_method_name
方法,而run_method_name
是在 vmSymbols.hpp 用宏定義的:
class vmSymbolHandles: AllStatic {
...
template(run_method_name,"run") //LOOK!!! 這裏決定了調用的方法名稱是 “run”!
...
}
Runnable()接口實現多線程
Thread類的核心功能是進行線程的啓動。如果一個類爲了實現多線程直接去繼承Thread類就會有單繼承侷限。在 java中又提供有另外一種實現模式:Runnable接口
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
利用Runable接口實現線程主體類
- 利用Runable接口實現線程主體類
class MyThread implements Runnable {
private int ticket = 10;
@Override
public void run() {
while (this.ticket > 0)
System.out.println(Thread.currentThread().getName()+"還剩下"+this.ticket--+"票");
}
}
public class TestThread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread,"黃牛1").start();
new Thread(myThread,"黃牛2").start();
}
}
- 使用匿名內部類進行Runnable對象創建
public class TestThread {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
private int ticket = 100;
@Override
public void run() {
while(this.ticket > 0)
System.out.println(Thread.currentThread().getName() + "還剩下" + this.ticket-- + "票");
}
};
new Thread(runnable,"黃牛1").start();
new Thread(runnable,"黃牛2").start();
}
}
- 使用Lamdba表達式進行Runnable對象創建
public class TestThread {
public static void main(String[] args) {
Runnable runnable = () -> {
int ticket = 10;
while (ticket > 0){
System.out.println(Thread.currentThread().getName() + "還剩下" + ticket-- + "票");
}
};
new Thread(runnable,"黃牛1").start();
new Thread(runnable,"黃牛2").start();
}
}
繼承Thread類與實現Runnable接口的關係
- Thread類與自定義線程類(實現了Runnable接口),是一個典型的代理設計模式。Thread類負責輔助真實業務操作(資源調度,創建線程並啓動)。自定義線程類負責真實業務的實現(run方法具體要做的事)。
- 使用Runnable接口實現的多線程程序類可以更好的描述貢獻的概念。
Callable實現多線程
從JDK1.5開始追加了新的開發包:java.uti.concurrent。這個開發包主要是進行高併發編程使用的,包含很多在高併發操作中會使用的類。在這個包裏定義有一個新的接口Callable
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Runnable中的run()方法沒有返回值,它的設計也遵循了主方法的設計原則:線程開始了就別回頭。但是很多時候需要一些返回值,例如某些線程執行完成後可能帶來一些返回結果,這種情況下就只能利用Callable來實現多線程。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String>{
private int ticket = 1000;
@Override
public String call() {
while (this.ticket > 0){
System.out.println(Thread.currentThread().getName() + "還剩下" + ticket-- + "票");
}
return "票賣完了!!!";
}
}
public class TestThread {
public static void main(String[] args) throws ExecutionException,InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new MyThread());
new Thread(futureTask,"黃牛1").start();
new Thread(futureTask,"黃牛2").start();
System.out.println(futureTask.get());
}
}