不知道從什么時候開始,學習知識變成了一個短期記憶的過程,總是容易忘記自己當初學懂的知識(fuck!),不知道是自己沒有經常使用還是當初理解的不夠深入.今天准備再對java的線程進行一下系統的學習,希望能夠更好的理解使用java線程.
1. 什么是線程,線程與進程的差別?(這一塊內容我想我已經有了一個理解,這里就不再做記錄了)
2.java線程的狀態:
從百度上隨便找了一張圖,圖中已經很清楚的標注了thread的各個狀態以及狀態的變化的場景.我們會在接下來的章節中進行相關講解.
3.java實現多線程的方式:
A: 繼承Thread類:(在下面的章節進行源碼分析)
public class ThreadTest { public static void main(String[] args) { Thread1 thread1 = new Thread1(); thread1.start(); } } class Thread1 extends Thread{ public void run () { try { Thread.sleep(100L); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
B: 實現Runnable接口.
public class ThreadTest { public static void main(String[] args) { Thread thread2 = new Thread(new Thread2()); thread2.start(); } } class Thread2 implements Runnable{ @Override public void run() { try { Thread.sleep(100L); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
4. 實現多線程的兩種方法的差別:
A. 眾所周知java不允許多繼承,那么我們集成Runnable接口實現多集成就能夠很好的避免這個限制.
B.集成Runnable接口實現多線程有利於程序操作共享資源(后面會提到)
這個理解起來很簡單: 我們繼承了Thread類實現run方法之后我們可以發現這樣一個問題,我們再進行線程實例化之后我們必須分別啟動線程任務.
而我們實現Runnable接口的話,我們可以實例化多個Thread類來運行這個任務.
當然集成Thread類也並不是不能完成共享資源的分發,而是比較費勁.
5. 實例化:我們在初始化Thread類的時候會調用Thread內部的init方法,即便是我們不提供任何參數.init函數的結構: private void init(ThreadGroup g, Runnable target, String name,long stackSize)
參數有:ThreadGroup,Target,name.stackSize,其中ThreadGroup會遞歸去調用父類的getThreadGroup來進行初始化,等待初始化完成之后我們會通過ThreadGroup調用checkAccess()方法來檢查當前線程是否有權限操作此線程.
java源碼: Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess();
其中Thread類的daemon,priority屬性會由父類繼承.
6.Thread類中的方法:
Thread.sleep(): 此方法調用的是native的方法,本人不才,記得當初看過jdk源碼,但是並沒看懂底層實現。sleep方法是使當前線程休眠,講cpu占用權交給其他任意優先級的線程。但是我們應該注意:sleep方法並不會釋放對象鎖。
Thread.join(): 記得當初查看api的時候覺得api對join方法的解釋非常模糊。到底是誰等待誰結束,這有歧義。其實是這樣的,在java7 api中介紹的很清楚,是調用join的線程等待被調用線程執行結束之后再開始執行。這里有一個很值得注意的問題,join的底層調用的是wait方法,而且是循環調用,源碼如下:
long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } }
我們可以看到源碼中join方法會在while循環中一直調用wait方法,這是因為如果wait的時間是1000ms,如果在100ms的時候另外一個線程調用了notifyAll方法,那么線程就會蘇醒。還要注意第二個問題,就是join調用wait方法,那么我們知道當main線程調用ThreadA.join的時候,main函數會獲取ThreadA對象的鎖。當ThreadA線程執行完成之后釋放該對象鎖。下面我們通過一個例子來驗證一下上面的論述:我們新建三個線程,B,C,D,然后在B-C-D中進行循環調用。
public class ThreadTest { public static ThreadB threadB = new ThreadB(); public static void main(String[] args) throws InterruptedException{ System.out.println("main線程開始調用B.join"); threadB.start(); threadB.join(); } } class ThreadB extends Thread{ public void run(){ try { System.out.println("ThreadB執行ThreadC.join"); ThreadC threadC = new ThreadC(); threadC.start(); threadC.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ThreadC extends Thread{ public void run(){ try { System.out.println("ThreadC執行ThreadD.join"); ThreadD threadD = new ThreadD(); threadD.start(); threadD.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ThreadD extends Thread{ public void run(){ try { System.out.println("ThreadD執行ThreadB.join"); ThreadTest.threadB.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } 結果: main線程開始調用B.join ThreadB執行ThreadC.join ThreadC執行ThreadD.join ThreadD執行ThreadB.join
可以發現我們的程序一直停留在這個位置,這是因為三個線程滿足了死鎖的條件,同時也可以證明,thread.join()的調用者必定會獲取被調用者的鎖。
Thread.yield: 此方法與sleep方法類似,但是需要注意一個問題就是Thread.yield只能講cpu的使用權轉交給同等優先級的線程。
Thread.start: 最后我們談一談Thread.start方法,想必大家都知道Thread.start方法會啟動線程,並且執行run方法中的內容。你是否會想我們為什么不直接調用Thread.run來執行呢?其實是這樣的,如果我們調用Thread.run來執行的話,jvm並不會真正的啟動一個線程,而是將其當做一個普通的方法執行。而調用start的話,在start內部會調用start0方法來新建一個線程。
至此: 線程的基礎知識就結束了,下一章我們會學習關於線程鎖的相關知識。
