一、線程的基本概念
JAVA Thread代碼中的狀態枚舉代碼爲:
public enum State { //初始化Thread後但是未啓動 NEW, //就緒狀態的線程,但是也有可能因爲來自操作系統的某些原因造成等待,隨時等待CUP進行調用。調用了start方法,也不一定馬上就能運行。 RUNNABLE, //阻塞狀態,運行中的線程因爲原因造成了阻塞,直到進入就緒狀態,才能被CPU重新調用。 BLOCKED, //等待狀態,包括三個方法:wait()、join()、LockSupport.park()方法。 //例如調用wait()方法後等待其他線程調用notify()與notifyAll()。 WAITING, //該狀態爲一個具體時間限制的等待狀態。 //如以下方法:sleep(timeout)、wait(timeout)、join(timeout)、LockSupport.parkNanos、LockSupport.parkUntil TIMED_WAITING, //一個線程已經完成執行或者被打斷狀態 TERMINATED; }
線程生命週期示意圖:
二、java.lang.Thread
1. 構造函數:
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { //name必須不能爲空。 if (name == null) { throw new NullPointerException("name cannot be null"); } // private volatile char name[];爲name的聲明 this.name = name.toCharArray(); //父線程爲啓動線程 Thread parent = currentThread(); //打開java的安全管理器 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(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }
2. run和start
@Override public void run() { // private Runnable target;該target可以爲runnable的run。適用於實現runnable接口的初始化線程的方法。 if (target != null) { target.run(); } }
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();
3.join()主要是等待該線程死亡。主要作用是該線程執行完成後纔會運行join()後面的方法。
public final void join() throws InterruptedException { join(0); } public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; //如果時間小於0,則返回異常 if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { //如果時間爲0,則一直等待直到當前線程死亡,即isAlive爲false;每次循環等待的時間爲0 while (isAlive()) { wait(0); } } else { //如果時間大於0,需要判斷當前時間與join方法調用時間之間的差距,如果時間差大於參數設置的時間,則不再等待循環結束直接跳出 while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
例子:1.不調用join的情況
public class TestThread { public static void main(String[] args) throws InterruptedException { MyThread thread = new MyThread(); MyThread1 thread1 = new MyThread1(thread); thread1.start(); } } class MyThread1 extends Thread { MyThread myThread; public MyThread1(MyThread myThread) { this.myThread = myThread; } public void run() { myThread.start(); System.out.println("end"); } } class MyThread extends Thread { public void run() { int i = 0; while (true) { System.out.println(this.toString() + ":" + (i++)); if(i==5){ break; } try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
輸出結果爲:
end Thread[Thread-0,5,main]:0 Thread[Thread-0,5,main]:1 Thread[Thread-0,5,main]:2 Thread[Thread-0,5,main]:3 Thread[Thread-0,5,main]:4
可以看到並沒有相應的順序和規律。
2.調用join()無參數的情況
修改MyThread1中的run()方法:
public void run() { myThread.start(); try { myThread.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("end"); }
輸出結果爲:
Thread[Thread-0,5,main]:0 Thread[Thread-0,5,main]:1 Thread[Thread-0,5,main]:2 Thread[Thread-0,5,main]:3 Thread[Thread-0,5,main]:4 end
可以看到一直運行MyThread這個線程,不會運行MyThread1線程join方法下面的代碼,這是由於會一直等待MyThread線程死亡,纔會運行主線程的代碼。
3.調用join()有參數的情況
修改MyThread1中的run()方法:
public void run() { myThread.start(); try { myThread.join(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("end"); }
輸出結果爲:
Thread[Thread-0,5,main]:0 end Thread[Thread-0,5,main]:1 Thread[Thread-0,5,main]:2 Thread[Thread-0,5,main]:3 Thread[Thread-0,5,main]:4
可以看到在MyThread開始後,調用join(1000)後,會等待1000毫秒後,運行MyThread1的打印代碼。而不是無限等待MyThread的死亡。
該方法的主要內容可以保證主線程不會優先於子線程先死亡,即如果主線程啓動一個子線程,可以在子線程中調用主線程的join方法,這樣就可以保證子線程死亡後主線程纔會繼續往下運行。
三、java.lang.runnable
runnable爲接口,只有一個抽象方法run。必須實現該方法才能實現線程的調用。代碼如下:
public interface Runnable { public abstract void run(); }
四、java.util.concurrent.Callable與java.util.concurrent.FutureTask
1.該類爲第三種初始化線程的方法。即實現Callable中call方法,並用FutureTask包裝Callable類,通過Thread包裝FutureTask之後調用start方法開啓線程。call方法有返回值。調用方式如下:
public class TestCallable implements Callable<String> { public static void main(String[] args) { TestCallable testCallable=new TestCallable(); FutureTask futureTask=new FutureTask<String>(testCallable); for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+"循環變量i的值"+i); if(i==70){ new Thread(futureTask,"有返回的線程").start(); } if(i==80){ try { System.out.println("子線程返回值"+futureTask.get()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } @Override public String call() throws Exception { int i = 0; for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + ":" + i); } return String.valueOf(i); } }
2.java.util.concurrent.Callable的代碼爲:可見邏輯主要是通過實現call的方法,並且有返回值
public interface Callable<V> { V call() throws Exception; }
3.java.util.concurrent.FutureTask
構造函數主要是包裝Callable類。該類實現接口interface RunnableFuture<V> extends Runnable, Future<V>,因此可以使用Thread進行包裝並調用start啓動線程
public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable }
實現接口的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) { //result爲callable的call()方法的返回值 V result; boolean ran; try { //調用call並且將ran設置爲true result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } //將結果傳給futureTask的對象 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); } }
五、Thread、runnable與callable的區別。
1.runnable與callable初始化後必須作爲Thread的參數進行封裝,並調用Thread的start方法進行啓動線程。如下:
MyThread thread = new MyThread(); thread.start(); MyRunable runable=new MyRunable(); Thread runnableThread=new Thread(runable); runnableThread.start();
2.runnable與callable只有一個方法,如果需要調用sleep等方法,需要調用Thread中的方法。
3.建議使用runnable與callable實現線程的class,主要是因爲runnable爲接口,java中不支持多繼承,但支持多接口,如果繼承Thread來實現線程,那麼就無法再繼承別的父類了。
4.runnable實現run方法;callable實現call方法,有返回值,有異常返回。