java如何編寫多線程

1.如何實現多線程安全

1.1實現Runnable接口,實現run()方法。多線程

public class Main4 implements Runnable { public static void main(String[] args) { Main4 m = new Main4(); new Thread(m).start(); } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(i); } } }

1.2繼承Thread接口,重寫run()方法。app

public class Main4 extends Thread { public static void main(String[] args) { new Main4().start(); } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(i); } } }

1.3實現Callable接口,實現call()方法。less

public class Main4 implements Callable { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable c = new Main4(); FutureTask<Integer> ft = new FutureTask<>(c); new Thread(ft).start(); System.out.println(ft.get()); } @Override public Integer call() throws Exception { int i = 0; for (i = 0; i < 10; i++) {} return i; } }

2.Runnable、Thread、Callable三種方式實現多線程的區別ide

2.1Runnable函數

@FunctionalInterface public interface Runnable { public abstract void run(); }

Runnable接口很簡單,裏面只有一個抽象方法run()。run()方法裏面的是這個線程要執行的內容。ui

2.2Threadthis

public class Thread implements Runnable { public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } Thread(Runnable target, AccessControlContext acc) { init(null, target, "Thread-" + nextThreadNum(), 0, acc); } public Thread(ThreadGroup group, Runnable target) { init(group, target, "Thread-" + nextThreadNum(), 0); } public Thread(String name) { init(null, null, name, 0); } public Thread(ThreadGroup group, String name) { init(group, null, name, 0); } public Thread(ThreadGroup group, String name) { init(group, null, name, 0); } public Thread(Runnable target, String name) { init(null, target, name, 0); } public Thread(ThreadGroup group, Runnable target, String name) { init(group, target, name, 0); } public Thread(ThreadGroup group, Runnable target, String name, long stackSize) { init(group, target, name, stackSize); } }

Thread類實現了Runnable接口,所以咱們繼承Thread,實際上也是間接的實現了Runnable接口。spa

Thread中一共有9個構造函數,可是裏面實際調用的分別是:線程

init(ThreadGroup g, Runnable target, String name, long stackSize)
init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc)

咱們查看了第一個init()方法源碼,在內部實際上是調用了第二個init方法,將最後一個參數置空。所以咱們只要詳細看5個參數的init()方法便可。

private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null); //內部其實調用了另外一個init方法 } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { if (name == null) {throw new NullPointerException("name cannot be null"); } this.name = name; //指定線程名稱 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(); /* * 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); //設置優先級(1-10),不在範圍內則拋出異常。因爲線程組的最大優先級能夠設置,參數大於線程組的最大優先級,取線程組最大優先級。 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(); //線程的惟一id }

2.3Callable

@FunctionalInterface public interface Callable<V> { V call() throws Exception; }

Callable接口也很簡單,裏面只有一個方法call()。

使用Callable時,須要使用FutureTask類進行調用。

查看FutureTask類的繼承關係,可知其最上面也是實現了Runnable接口。

查看FutureTask的構造函數,一共有兩個。

FutureTask(Callable<V> callable):futureTask內部有一個私有變量Callable,令其等於傳入的callable。
FutureTask(Runnable runnable, V result):調用Executors的靜態方法,建立內部一個實現了callable接口的內部類,call()方法執行Runnable的run(),執行成功後返回result。
源碼以下:
public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW;       // ensure visibility of callable
 } public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); //見下方 this.state = NEW;       // ensure visibility of callable
    }
public static <T> Callable<T> callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); return new RunnableAdapter<T>(task, result); } static final class RunnableAdapter<T> implements Callable<T> { final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); return result; } }

在FutureTask中定義了運行狀態一共有7種(注意它們各自的數值,會常常使用>=、<=等方式來處理邏輯走向):

/* * NEW -> COMPLETING -> NORMAL 新建->執行->完成 * NEW -> COMPLETING -> EXCEPTIONAL 新建->執行->異常 * NEW -> CANCELLED 新建->取消 * NEW -> INTERRUPTING -> INTERRUPTED 新建->中斷運行->中斷狀態 */
    private volatile int state; private static final int NEW          = 0; private static final int COMPLETING   = 1; private static final int NORMAL       = 2; private static final int EXCEPTIONAL  = 3; private static final int CANCELLED    = 4; private static final int INTERRUPTING = 5; private static final int INTERRUPTED  = 6;

FutureTask的run()方法,內部核心代碼是調用了callable.call()。若是順利執行,會執行set(result)方法,將結果保存到成員變量private Object outcome中。

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(); 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的get()方法,在線程t1中調用線程t2的get()方法,可見若是t2的run()仍未執行完成,則會一直等待執行完成後,獲取返回值,纔會繼續往下執行t1。

public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); //若是線程剛被新建,或正在運行,等待執行完成。 return report(s); //返回run()方法中保存的outcome } private int awaitDone(boolean timed, long nanos) throws InterruptedException { final long deadline = timed ? System.nanoTime() + nanos : 0L; //在線程類中,0一般用來表明不限制時間 WaitNode q = null; boolean queued = false; for (;;) { if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); } int s = state; if (s > COMPLETING) { if (q != null) q.thread = null; return s; } else if (s == COMPLETING) // cannot time out yet 
 Thread.yield(); //仍在執行中,令當前線程讓出cpu資源。由於一般是在一個線程t1裏調用另外一個線程t2的get()方法,即令t1讓出cpu,t2能夠參與競爭 else if (q == null) q = new WaitNode(); else if (!queued) queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q); else if (timed) { nanos = deadline - System.nanoTime(); if (nanos <= 0L) { removeWaiter(q); return state; } LockSupport.parkNanos(this, nanos); } else LockSupport.park(this); } }

 

3總結

實際上不管是使用哪種方式實現,最後調用時都是須要使用Thread類的start()方法進行調用。所以,線程的主要類,咱們研究Thread類便可。

線程的狀態state,在Thread類中之內部枚舉的形式存在

public enum State { NEW, //新建狀態,還沒有執行start方法 RUNNABLE, //可運行狀態,已經執行start方法,競爭cpu資源 BLOCKED, //阻塞狀態,等待獲取鎖進入代碼塊 WAITING, //等待狀態,線程進入此狀態只有三種方法:wait()、join()、park(),注意這些方法都沒有參數,即不會因爲超時問題而從新變爲可運行或執行狀態 TIMED_WAITING, //定時等待狀態,進入此方法的方式與waiting相似,可是該方法有時間限制,當達到指定時間後會從新變爲Runnable,如sleep、wait(long)等 TERMINATED; //終止狀態,線程已經執行完畢 }

幾種經常使用方法介紹:

yield():當前線程暫停執行,讓出cpu,從新競爭。有可能仍然是該線程競爭到cpu。
sleep(long):當前線程暫停執行(不釋放鎖),休眠指定毫秒數,其餘線程競爭cpu,當指定時間過去,當前線程繼續執行。
interrupt():中斷當前線程,一般用來讓內部是while(!Thread.currentThread().isInterrupt())的run()方法中斷運行。
wait():令當前線程進入等待狀態(釋放鎖),只能使用在synchronized塊中。所以,當線程執行wait方法的時候必定是取得了鎖。能夠經過notify()或notifyAll()方法從新喚醒
join():一般是在一個線程t1裏面調用另外一個線程t2的join方法,當t1執行到這裏的時候,會獲取t2的鎖並執行t2,直到t2執行完畢,再繼續執行t1下面的步驟。
join(long):與join()相似,不一樣處在於t1最多隻會等待long秒,當時間到達後,若是t2仍沒有執行完畢,那麼t1也會繼續執行下面的步驟。

 join()方法例子:

public class Main4 extends Thread { public static void main(String[] args) throws ExecutionException, InterruptedException { Main4 m1 = new Main4(); Main4 m2 = new Main4(); m1.start(); m1.join(); System.out.println("---------------main---------------"); m2.start(); } @Override public void run() { int i = 0; for (i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "[i="+i+"]"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }

如上程序,控制檯會先打印m1線程的0-9,而後再打印"---main---",最後打印m2線程的0-9.

若是咱們將m1.join()改成m1.join(1000),那麼會先打印m1的0,這時達到參數1000ms,main線程會繼續並行往下執行,打印"---main---",而後啓動m2線程,m1與m2爭奪cpu競相打印。

須要注意的是,join(0)不是等待0ms,而是等價於join()方法。源碼中join()內部只有一行代碼:join(0)。

 

 

_

相關文章
相關標籤/搜索