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()方法。

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()方法。

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三種方式實現多線程的區別

2.1Runnable

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

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

2.2Thread

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接口。

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)。

 

 

_


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM