對於 Java 多線程編程中的 implements Runnable 與 extends Thread,部分同窗可能會比較疑惑,它們之間究竟有啥區別和聯繫呢?他們是否是沒啥區別隨便選呢?實際中究竟該選擇哪個呢?html
甚至網上很多博客文章以訛傳訛得出很多謬論,那今天的走進科學欄目將帶您一一揭開謎底。java
其實這塊主要是圍繞着接口和抽象類的區別以及一些設計原則而言的。面試
The limitation with "extends Thread" approach is that if you extend Thread, you can not extend anything else . Java does not support multiple inheritance. In reality , you do not need Thread class behavior , because in order to use a thread you need to instantiate one anyway. On the other hand, Implementing the Runnable interface gives you the choice to extend any class you like , but still define behavior that will be run by separate thread.編程
In "implements Runnable" , we are creating a different Runnable class for a specific behavior job (if the work you want to be done is job). It gives us the freedom to reuse the specific behavior job whenever required. "extends Thread" contains both thread and job specific behavior code. Hence once thread completes execution , it can not be restart again. 安全
Implementing Runnable should be preferred . It does not specializing or modifying the thread behavior . You are giving thread something to run. We conclude that Composition is the better way. Composition means two objects A and B satisfies has-a relationship. "extends Thread" is not a good Object Oriented practice.多線程
"implements Runnable" makes the code loosely-coupled and easier to read . Because the code is split into two classes . Thread class for the thread specific code and your Runnable implementation class for your job that should be run by a thread code. "extends Thread" makes the code tightly coupled . Single class contains the thread code as well as the job that needs to be done by the thread.併發
"extends Thread" means inheriting all the functions of the Thread class which we may do not need . job can be done easily by Runnable without the Thread class functions overhead.app
至此,我的是推薦優先選擇 implements Runnable 。dom
public class Thread extends Object implements Runnable
追蹤Thread中的start()方法的定義,能夠發現此方法中使用了private native void start0();其中native關鍵字表示能夠調用操做系統的底層函數,這樣的技術稱爲JNI技術(java Native Interface)。函數
可是在使用Runnable定義的子類中沒有start()方法,只有Thread類中才有。此時觀察Thread類,有一個構造方法:public Thread(Runnable targer),此構造方法接受Runnable的子類實例,也就是說能夠經過Thread類來啓動Runnable實現的多線程。
可是能夠看到它們子線程運行的位置不一樣,Thread運行在父類的run方法中,Runnable運行在實現Runnable接口的子類對象run方法中。
有同窗的例子是這樣的,參考:http://developer.51cto.com/art/201203/321042.htm:
package tmp; class MyThread extends Thread { private int ticket = 10; private String name; public MyThread(String name) { this.name = name; } public void run() { for (int i = 0; i < 500; i++) { if (this.ticket > 0) { System.out.println(this.name + "賣票---->" + (this.ticket--)); } } } } public class ThreadDemo { public static void main(String[] args) { MyThread mt1 = new MyThread("一號窗口"); MyThread mt2 = new MyThread("二號窗口"); MyThread mt3 = new MyThread("三號窗口"); mt1.start(); mt2.start(); mt3.start(); } } // 一號窗口賣票---->10 // 二號窗口賣票---->10 // 二號窗口賣票---->9 // 二號窗口賣票---->8 // 三號窗口賣票---->10 // 三號窗口賣票---->9 // 三號窗口賣票---->8 ...
Runnable 代碼:
package tmp; class MyThread1 implements Runnable { private int ticket = 10; private String name; public void run() { for (int i = 0; i < 500; i++) { if (this.ticket > 0) { System.out.println(Thread.currentThread().getName() + "賣票---->" + (this.ticket--)); } } } } public class RunnableDemo { public static void main(String[] args) { MyThread1 mt = new MyThread1(); Thread t1 = new Thread(mt, "一號窗口"); Thread t2 = new Thread(mt, "二號窗口"); Thread t3 = new Thread(mt, "三號窗口"); t1.start(); t2.start(); t3.start(); } } // 二號窗口賣票---->10 // 三號窗口賣票---->9 // 三號窗口賣票---->7 // 一號窗口賣票---->9 // 三號窗口賣票---->6 // 二號窗口賣票---->8 // 三號窗口賣票---->4 // 一號窗口賣票---->5 // 三號窗口賣票---->2 // 二號窗口賣票---->3 // 一號窗口賣票---->1
由此差異,有同窗就得出了一個結論:用Runnable就能夠實現資源共享,而 Thread 不能夠,這是他們的主要差異之一。。。
其實仔細看看代碼就知道,這只是兩種寫法的區別,根本就不是 implements Runnable 與 extends Thread 的區別:
MyThread1 mt = new MyThread1(); Thread t1 = new Thread(mt,"一號窗口"); Thread t2 = new Thread(mt,"二號窗口"); Thread t3 = new Thread(mt,"三號窗口"); //////////////// Thread t1 = new Thread(new MyThread1(),"一號窗口"); Thread t2 = new Thread(new MyThread1(),"二號窗口"); Thread t3 = new Thread(new MyThread1(),"三號窗口");
其實,想要「資源共享」,Thread 也能夠作到的:
private static int ticket = 10; // 三號窗口賣票---->10 // 一號窗口賣票---->9 // 二號窗口賣票---->9 // 一號窗口賣票---->7 // 一號窗口賣票---->5 // 三號窗口賣票---->8 // 一號窗口賣票---->4 // 二號窗口賣票---->6 // 一號窗口賣票---->2 // 三號窗口賣票---->3 // 二號窗口賣票---->1
經過 static 就能夠實現擁有共同的ticket=10,但問題也來了,你會發現一二號窗口都賣了第 9 張票。
上面的例子以及結果證實了多線程場景下,須要留意線程安全的問題:
public synchronized void run()
synchronized (Test.class)
private static final Object countLock = new Object(); synchronized (countLock) { count++; }
package tmp; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; public class Demo implements Runnable { String name; // static Integer tickets = 20; private static AtomicInteger tickets = new AtomicInteger(20); public Demo(String name) { this.name = name; } public void run() { for (int i = 1; i <= 20; i++) { synchronized (tickets) { if (tickets.get() > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println("我取票第" + ": " + tickets.getAndDecrement() + " 張票。"); // tickets--; try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println("==========如今查詢還剩" + ": " + tickets.get() + " 張票。"); } } } } public static void main(String[] args) throws IOException { Demo demo = new Demo("hello"); new Thread(demo).start(); new Thread(demo).start(); new Thread(demo).start(); } }
到這兒,本期走進科學也要跟你們說聲再見了,其實聊着聊着感受都快跑題了,多線程這塊話題不少,很複雜,須要慢慢實踐與積累,祝你們玩的愉快。
package thread; /** 題目: 要求用三個線程,按順序打印1,2,3,4,5.... 71,72,73,74, 75. 線程1先打印1,2,3,4,5, 而後是線程2打印6,7,8,9,10, 而後是線程3打印11,12,13,14,15. 接着再由線程1打印16,17,18,19,20....以此類推, 直到線程3打印到75。 分析:感受出題人是要考察一下你是否可以很好的控制多線程,讓他們有序的進行。 一、線程池:3個線程,須要使用併發庫的線程池 二、鎖(lcok):在打印的時候,只容許一個線程進入,其餘的線程等待 * Date: 2013-6-23 上午3:28:56 <br/> * @author http://hi.baidu.com/leejun_2005/item/1a7bb2085db78a1deafe38ba */ import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class NumberPrinter { private Lock lock = new ReentrantLock(); private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); private Condition c3 = lock.newCondition(); private Map<Integer, Condition> condtionContext = new HashMap<Integer, Condition>(); public NumberPrinter() { condtionContext.put(Integer.valueOf(0), c1); condtionContext.put(Integer.valueOf(1), c2); condtionContext.put(Integer.valueOf(2), c3); } private int count = 0; public void print(int id) { lock.lock(); try { while(count*5 < 75) { int curID = calcID(); if (id == curID) { for (int i = 1; i<=5; i++) { System.out.println(Thread.currentThread().getName() + " -- " + (count*5 +i) + ","); } System.out.println(); count++; int nextID = calcID(); Condition nextCondition = condtionContext.get( Integer.valueOf(nextID)); //通知下一線程 nextCondition.signal(); } else { Condition condition = condtionContext.get( Integer.valueOf(id)); condition.await(); } } //通知線程結束 for(Condition c : condtionContext.values()) { c.signal(); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } private int calcID() { // TODO Auto-generated method stub return count % 3; } /** * @param args */ public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); final CountDownLatch latch = new CountDownLatch(1); final NumberPrinter printer = new NumberPrinter(); for (int i = 0; i < 3; i++) { final int id = i; executor.submit(new Runnable() { public void run() { // TODO Auto-generated method stub try { latch.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } printer.print(id); } }); } System.out.println("三個任務開始順序打印數字。。。。。。"); latch.countDown(); executor.shutdown(); } }
三個任務開始順序打印數字。。。。。。 pool-1-thread-1 -- 1, pool-1-thread-1 -- 2, pool-1-thread-1 -- 3, pool-1-thread-1 -- 4, pool-1-thread-1 -- 5, pool-1-thread-2 -- 6, pool-1-thread-2 -- 7, pool-1-thread-2 -- 8, pool-1-thread-2 -- 9, pool-1-thread-2 -- 10, pool-1-thread-3 -- 11, pool-1-thread-3 -- 12, pool-1-thread-3 -- 13, pool-1-thread-3 -- 14, pool-1-thread-3 -- 15, pool-1-thread-1 -- 16, pool-1-thread-1 -- 17, pool-1-thread-1 -- 18, pool-1-thread-1 -- 19, pool-1-thread-1 -- 20, pool-1-thread-2 -- 21, pool-1-thread-2 -- 22, pool-1-thread-2 -- 23, pool-1-thread-2 -- 24, pool-1-thread-2 -- 25, pool-1-thread-3 -- 26, pool-1-thread-3 -- 27, pool-1-thread-3 -- 28, pool-1-thread-3 -- 29, pool-1-thread-3 -- 30, ...
辦公室只有一個衛生間,一次只能容納一我的方便,這個衛生間就是競爭條件(Race Condition)。當一我的進去後就在門口牌子上標識爲「有人」,這個就至關因而線程的加鎖,告訴其它同時間想要上廁所的人,這個資源已被我佔位,其餘人就須要等待,這叫wait。只有當前面的人出來後,並把牌子置爲「無人」時,其它人才有機會使用。當只有一個蹲位時,一次只能進一我的,翻動一塊牌子加一把鎖,這個就叫互斥鎖(Mutex)。若是衛生間裏有多個蹲位,再簡單地用一塊牌子來標識就不行了,須要作一個電子公告牌,進去一我的電子公告牌就把可用數量減1,出來一我的數量加1,數量不爲0時,有人來直接進去就好了不用等待,這個叫信號量(Semaphores)。若是出來的人是隨機通知等待的某一我的,這叫notify,若是他是對着全部等待的人喊一嗓子,就是notifyAll。若是使用notify,有些倒黴的傢伙可能永遠也不會被通知到,這太不人性了,而若是使用nofityAll就意味着全部等待的人須要競爭資源,仍是會在倒黴蛋永遠輪不到。解決的辦法一是按時間順序先到先得,順序進入,火車站的廁所常常會看到這種狀況,老是有機會輪到本身,這叫公平鎖(FairLock)。還有一種狀況,就是大老闆也在排隊,通常狀況下大老闆時間寶貴,能夠優先考慮讓他先上,這叫線程優先級,一共有10個級別。優先級只能保證級別高的優先被調度到,但不能保證必定會被調度到。
兩個好基友一塊兒在蹲坑,只有一卷手紙,一我的去取時另外一個就不能同時去取,這叫基於共享內存的線程間通訊。兩我的都是煙鬼,但只帶了一個打火機,一我的用完以後遞給另一我的,
進程和線程的區別:一個辦公區有多個衛生間,每一個衛生間的資源是獨立的,不會相互依賴,至關因而進程。每一個衛生間有多個蹲位,每一個蹲位至關因而一個線程,蹲位越多併發處理能力越強。但多個同一個衛生間的多個蹲位共用一個洗手檯,若是蹲位過多,洗手檯的資源會成爲瓶頸。
[1] Java線程面試題 Top 50