如何實現多執行緒?實現多執行緒為什麼要調start,而不是run方法?(繼承Thread類、實現Ruable介面、Callable<V>)
什麼是程序?
作業系統中一個程式的執行週期(從開啟到關閉)。程序是具有一個或多個執行緒的執行緒組。
什麼是執行緒?
- 一個程序可以同時執行多個任務,任務就是執行緒,一個程序至少有一個執行緒。
- 執行緒執行在程序內部,執行緒是輕量級程序。
程序和執行緒比較:
- 與程序相比,執行緒更加輕量級,建立,撤銷一個執行緒比啟動、撤銷一個程序開銷小的多。一個程序中的所有執行緒共享此程序的所有的資源;
- 沒有程序就沒有執行緒,程序一旦終止,其內的執行緒也不復存在;
- 程序是作業系統資源分配的基本單位,程序可以獨享資源;
執行緒需要依託程序提供的資源,無法獨立申請作業系統資源,是作業系統任務執行 (cpu排程) 的基本單位。
多執行緒的表現:360瀏覽器可以同時下載多個圖片或者響應使用者的其他請求;
在高峰期買票時,12306需要同時處理多個客戶的請求,但是請求數量過多,也就是訪問執行緒量過多,會導致12306伺服器崩潰…
實現多執行緒
實現多執行緒有三種方式:
- 繼承Thread類實現多執行緒
- Runable介面實現多執行緒
- 實現介面Callable來實現多執行緒(juc)
一:繼承Thread類實現多執行緒
java.lang.Thread 是執行緒操作的核心類。新建一個執行緒最簡單的方法是直接繼承Thread類,而後覆寫run( )方法(相當於主執行緒的main方法)。run是執行緒的入口方法。
///繼承Thread類實現多執行緒
class Mythread extends Thread
{
private String title;
public Mythread(String title)
{
this.title=title;
}
@Override
public void run() //run是執行緒的入口,相當於main
{
for(int i=0;i<10;i++)
{
System.out.println(title+" "+ i);
}
}
}
public class Thread1
{
public static void main(String[] args) {
//有3個執行緒
Mythread thread1=new Mythread("thread1");
Mythread thread2=new Mythread("thread2");
Mythread thread3=new Mythread("thread3");
thread1.run(); //執行緒直接呼叫run方法
thread2.run();
thread3.run();
}
}
從結果看出,三個執行緒順序列印,並沒有實現多執行緒,是因為實現多執行緒必須用Thread.start()方法。
任何啟動多執行緒的方式都是呼叫Thread類中的start()方法。
//Thread類的start方法
public class Thread1
{
public static void main(String[] args) {
Mythread thread1=new Mythread("thread1");
Mythread thread2=new Mythread("thread2");
Mythread thread3=new Mythread("thread3");
thread1.start(); //實現多執行緒必須用Thread類的start方法
thread2.start();
thread3.start();
}
}
從結果看書,呼叫start實現了多執行緒,即同時執行多個任務。
為什麼要呼叫start方法不能直接呼叫run方法實現多執行緒?
先看呼叫Thread.start( )方法實現多執行緒過程:
Thread.start( )原始碼:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0(); //呼叫start0方法
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
從start原始碼中發現會拋一個IllegalThreadStateException異常,按照異常處理方式,會在呼叫處處理該異常,但是呼叫處沒有處理並且沒有報錯,是因為IllegalThreadStateException是一個RunTime Exception異常即非受查異常,該異常是因為如果多次啟動同一個執行緒引起的。那麼,一個執行緒只能啟動一次。
並且在start原始碼中呼叫了start0方法,而且該方法是本地方法,即呼叫本機的原生系統函式。Thread 類有個 registerNatives 本地方法, registerNatives在靜程式碼塊中,也就是當類載入到JVM中會呼叫該方法,該方法主要的作用就是註冊一些本地方法供Thraed使用,比如start0和stop0等。
本地方法 registerNatives 是定義在 Thread.c 檔案中的。Thread.c 是個很小的檔案,它定義了各個作業系統平臺都要用到的關於執行緒的公用資料和操作,其中有start0,如下:
"start0", "()V",(void \*)&JVM_StartThread
而JVM_StartThread如下:
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(), //呼叫run_method_name
vmSymbolHandles::void_method_signature(),THREAD);
}
線上程函式 thread_entry中呼叫了vmSymbolHandles::run_method_name(),
而run_method_name是在 vmSymbols.hpp 用巨集定義的:
class vmSymbolHandles: AllStatic {
...
template(run_method_name,"run") // 這裡決定了呼叫的方法名稱是 “run”
...
}
最終呼叫了Java執行緒的入口方法run。
所以當啟動一個執行緒需要Java程式呼叫start方法,start方法中的本地方法start0由JVM呼叫來準備Thread類所需要的資源(如果直接呼叫run方法,將不會準備這些資源,那就不是一個執行緒),JVM呼叫start0後,會回撥run方法來執行的具體操作任務。
二:Runable介面實現多執行緒
Runable介面實現多執行緒解決了單繼承侷限。
Runable介面原始碼:
@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介面中只允許有一個run抽象方法,可以用子類實現,也可以用函式式介面。
但是我們知道啟動一個執行緒必須用Thread類的start方法,所以需要把Runable介面轉化為Thread類,通過Thread類的構造方法:
public Thread(Runnable target); //將Runnable介面物件轉化為Thread類
用Runnable介面實現多執行緒:
class MythreadImpl implements Runnable
{
private String title;
public MythreadImpl(String title)
{
this.title=title;
}
public void run()
{
for(int i=0;i<10 ;i++)
{
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
} //睡眠一會,更好看到多執行緒併發執行效果
System.out.println(title+":"+ i);
}
}
}
public class Thread1
{
public static void main(String[] args)
{
Runnable thread1=new MythreadImpl("thread1");
Runnable thread2=new MythreadImpl("thraed2");
Runnable thread3=new MythreadImpl("thraed3");
//3個執行緒
new Thread(thread1).start();
new Thread(thread2).start();
new Thread(thread3).start();
}
}
因為我們目的是用Thread類的start方法,所以可以用匿名內部類來建立Runable介面物件:
/////匿名內部類進行Runnable物件建立
public class Thread1
{
public static void main(String[] args)
{
//用Runable介面實現一個執行緒
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":" + i);
//Thread.currentThread().getName()獲取當前執行緒名稱,預設從Thread-0開始
}
}
}).start();
}
}
由於Runable介面被@FunctionalInterface註解,所以可以用lambda表示式實現Runable介面:
///lambada實現Runnable介面
public class Thread1
{
public static void main(String[] args) {
Runnable thread1 = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
};
Runnable thread2 = () -> {
for (int i = 5; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
};
//兩個執行緒同時執行
new Thread(thread1).start();
new Thread(thread2).start();
}
}
從結果看出,2個執行緒併發執行。
Thread類的Runable區別和聯絡:
- 通過看Thread類的原始碼發現,Thread類實現了Runable介面:
public
class Thread implements Runnable{}
那麼Thread類肯定覆寫了Runable介面的run抽象方法,如果是用Runable介面實現多繼承,Runable自定義子類也會覆寫run抽象方法,那麼就可以得出一個結論,Thread類與自定義執行緒類(實現了Runable介面),是一個典型的代理設計模式:
hread類負責輔助真實業務的實現,(資源排程,建立程序並實現)
自定義執行緒負責真實業務的實現(run方法具體做的事)。
- 使用Runbale介面實現的多執行緒程式類可以更好的描述共享概念。
一共10張票,3個執行緒買票,如果用同一個Runable介面物件,那麼這3個執行緒訪問的是同一個tickets:
/////Runable介面實現資料共享
class Mythread implements Runnable
{
private Integer tickets=10;
@Override
public void run()
{
while(tickets>0)
{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+"還剩票數:"+tickets--);
}
}
}
public class Thread1
{
public static void main(String[] args)
{
Mythread thread = new Mythread();
new Thread(thread, "A").start();
new Thread(thread, "B").start();
new Thread(thread, "C").start();
}
}
三:實現介面Callable來實現多執行緒(juc)
JDK1.5新匯入程式包java.util.concurrent.Callable,開發包主要是進行高併發程式設計使用的,包含很多在高併發操作中會使用的類。這個包裡的Callable介面的出現是為了多執行緒有返回值而存在的。
首先看Callabl介面原始碼:
@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;
}
FutureTask定義:
public class FutureTask<V> implements RunnableFuture<V>
{
....
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
...
//覆寫Runable介面的run方法
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call(); //呼叫call方法
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
}
FutureTask類實現介面RunableFuture;
RunableFuture介面定義:繼承Runable和Future(介面可以實現多繼承)
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
介面Future定義,可以取得Callable介面物件返回值
public interface Future<V> {
...
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
*/
V get() throws InterruptedException, ExecutionException;
}
從上圖可以瞭解到:如果要用Callable介面物件實現多執行緒,可以藉助FutureTask,因為FutureTask實現了RunnableFuture,而RunnableFuture繼承了Runable和Future,Future可以獲得Callable的call方法返回值,也就是說FutureTask繼承了Runable和Future,所以可以用Future這個中間媒介實現Callable介面到Thread類的轉化。
package CODE.多執行緒;
/////Callable介面實現多執行緒
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyCallImpl implements Callable<String>
{
private Integer tickets=50;
public String call() throws Exception
{
while(tickets>0)
{
System.out.println(Thread.currentThread().getName()+"剩餘票數:"+tickets--);
}
return "票已售罄";
}
}
public class Call
{
public static void main(String[] args) throws InterruptedException ,ExecutionException
{
FutureTask<String> task=new FutureTask<>(new MyCallImpl());
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
System.out.println(task.get()); //票已售罄
}
}
注:當執行緒有返回值時,只能用Callable介面實現多執行緒。