本文轉載自【微信公眾號:小碼逆襲,ID:gh_7c5a039380a0】經微信公眾號授權轉載,如需轉載與原文作者聯繫
情景再現
今天一個學妹跟我分享她今天的面試過程,她面試的是java開發崗,跟我說了面試官大概問了哪些問題,基本都是一些常規的基本知識,其中涉及到多線程的部分有這樣一個問題。
面試官:(一臉嚴肅)說一下Thread和Runable有什麼區別吧。學妹:(心裡美滋滋,這個我在博客上看過,我會)Thread和Runable有2點區別,1.Thread有單繼承的問題;2.Runnable便於實現資源共享,而Thread不能。舉個例子,火車站賣票...balabala...面試官點了點頭....(面試了好幾個問題,終於笑了)。學妹心裡覺得發揮的不錯。
我們先不評論學妹的面試是好是壞,回到「Thread和Runable有什麼區別?」這個問題本身,我覺的一個java基本功,底子厚的面試官就不會問這個問題。為什麼這麼說,你馬上就知道了,今天我們從兩個方面來證明我的觀點,不要再被一些實力並不強的面試官蒙蔽了。
大家都這麼寫不代表一定正確
網絡上流傳的最大的一個錯誤結論:Runnable更容易可以實現多個線程間的資源共享,而Thread不可以!
我在網上隨便搜了一下上面的問題,大家都認為他是對的,其實這是很不科學的,網上的例子就是學妹面試的時候說的那個例子:火車站賣票。我隨便一篇博客中截取了這個例子,我們一起來看一下。
繼承Thread類的方法
//例1
public class MyThreadWithExtends extends Thread {
private int tickets = 10;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(tickets>0){
System.out.println(Thread.currentThread().getName()+"--賣出票:" + tickets--);
}
}
}
public static void main(String[] args) {
MyThreadWithExtends thread1 = new MyThreadWithExtends();
MyThreadWithExtends thread2 = new MyThreadWithExtends();
MyThreadWithExtends thread3 = new MyThreadWithExtends();
thread1.start();
thread2.start();
thread3.start();
}
}
運行結果:
Thread-0--賣出票:10
Thread-2--賣出票:10
Thread-1--賣出票:10
Thread-2--賣出票:9
Thread-0--賣出票:9
Thread-2--賣出票:8
Thread-1--賣出票:9
Thread-2--賣出票:7
Thread-0--賣出票:8
Thread-2--賣出票:6
Thread-2--賣出票:5
Thread-2--賣出票:4
Thread-1--賣出票:8
Thread-2--賣出票:3
Thread-0--賣出票:7
Thread-2--賣出票:2
Thread-2--賣出票:1
Thread-1--賣出票:7
Thread-0--賣出票:6
Thread-1--賣出票:6
Thread-0--賣出票:5
Thread-0--賣出票:4
Thread-1--賣出票:5
Thread-0--賣出票:3
Thread-1--賣出票:4
Thread-1--賣出票:3
Thread-1--賣出票:2
Thread-0--賣出票:2
Thread-1--賣出票:1
Thread-0--賣出票:1
得出的結論是:每個線程都獨立,不共享資源,每個線程都賣出了10張票,總共賣出了30張。如果真賣票,就有問題了。
實現Runnable接口方式
//例2
public class MyThreadWithImplements implements Runnable {
MyThreadWithImplements myRunnable = new MyThreadWithImplements();
Thread thread1 = new Thread(myRunnable, "窗口一");
Thread thread2 = new Thread(myRunnable, "窗口二");
Thread thread3 = new Thread(myRunnable, "窗口三");
窗口二--賣出票:10
窗口三--賣出票:9
窗口一--賣出票:8
窗口三--賣出票:6
窗口三--賣出票:4
窗口三--賣出票:3
窗口三--賣出票:2
窗口三--賣出票:1
窗口二--賣出票:7
窗口一--賣出票:5
得出的結論是:每個線程共享了對象myRunnable的資源,賣出的總票數是對的 我把這個結論用刪除線標記,因為這句話是大錯特錯!不要再鄭重其事的跟別人講了。
看了上面的例子,你有沒有想是對是錯呢?
例一這個例子結果多賣兩倍票的原因根本不是因為Runnable和Thread的區別,看其中這三行代碼:
MyThreadWithExtends thread1 = new MyThreadWithExtends();
MyThreadWithExtends thread3 = new MyThreadWithExtends();
創建了三個MyThreadWithExtends對象,每個對象都有自己的ticket成員變量,當然會多賣1倍。如果把ticket定義為static類型,就離正確結果又近了一步(因為是多線程同時訪問一個變量會有同步問題,加上鎖才是最終正確的代碼,這裡就不多提了)。
例二的代碼:
MyThreadWithImplements myRunnable = MyThreadWithImplements(); Thread thread3 = new Thread(myRunnable, "窗口三");只創建了一個Runnable對象,肯定只賣一倍票(但也會有多線程同步問題,同樣需要加鎖),根本不是Runnable和Thread的區別造成的。那么正確的寫法是什麼樣的呢?
public class Test extends Thread {
//十張票
private int ticket = 10;
public void run(){
for(int i =0;i<10;i++){
synchronized (this){
if(this.ticket>0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"賣票---->"+(this.ticket--));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] arg){
Test t = new Test();
new Thread(t,"線程1").start();
new Thread(t,"線程2").start();
}
線程1賣票---->10
線程1賣票---->9
線程1賣票---->8
線程1賣票---->7
線程1賣票---->6
線程1賣票---->5
線程1賣票---->4
線程1賣票---->3
線程1賣票---->2
線程1賣票---->1
看到了嗎,只創建了一個Thread對象,效果是不是跟Runable一樣呢,synchronized這個關鍵字是必須的,否則會出現同步問題,至於鎖的問題在這裡就不詳述了。
所以我們得出真正的結論:Thread和Runnable沒有根本的沒區別,只是寫法不同罷了,事實是Thread和Runnable沒有本質的區別,這才是正確的結論,和自以為是的大神所說的Runnable更容易實現資源共享,沒有半點關係!
Thread和Runable到底有什麼區別呢?
我們從源碼來看,上源碼:
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();
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
private volatile String name;
private int priority;
private Thread threadQ;
private long eetop;
/* Whether or not to single_step this thread. */
private boolean single_step;
/* Whether or not the thread is a daemon thread. */
private boolean daemon = false;
/* JVM state */
private boolean stillborn = false;
/* What will be run. */
private Runnable target;
/* The group of this thread */
private ThreadGroup group;
/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;
/* The inherited AccessControlContext of this thread */
private AccessControlContext inheritedAccessControlContext;
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
* The requested stack size for this thread, or 0 if the creator did
* not specify a stack size. It is up to the VM to do whatever it
* likes with this number; some VMs will ignore it.
private long stackSize;
* JVM-private state that persists after native thread termination.
private long nativeParkEventPointer;
* Thread ID
private long tid;
/* For generating thread ID */
private static long threadSeqNumber;
/* Java thread status for tools,
* initialized to indicate thread 'not yet started'
private volatile int threadStatus = 0;
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* Subclasses of <code>Thread</code> should override this method.
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
@Override
public void run() {
if (target != null) {
target.run();
''''''''此處省略眾多方法和屬性。
從上面的源碼可以看出:Thread實現了Runnable接口,提供了更多的可用方法和成員而已。
總結
Runable是一個接口,Thread是其實現類,只是具有很多自己獨特的屬性和方法。二者談區別沒有可比性。無論使用Runnable還是Thread,都會new Thread,然後執行run方法。用法上,如果有複雜的線程操作需求,那就選擇繼承Thread,如果只是簡單的執行一個任務,那就實現runnable。如果面試官問到你這個問題,把真相告訴他,你想看面試官目瞪口呆嗎?