java多線程的6種實現方式詳解

多線程的形式上實現方式主要有兩種,一種是繼承Thread類,一種是實現Runnable接口。本質上實現方式都是來實現線程任務,而後啓動線程執行線程任務(這裏的線程任務實際上就是run方法)。這裏所說的6種,實際上都是在以上兩種的基礎上的一些變形。java

第一種方式:繼承Thread類
萬物皆對象,那麼線程也是對象,對象就應該可以抽取其公共特性封裝成爲類,使用類能夠實例化多個對象,那麼實現線程的第一種方式就是繼承Thread類的方式。繼承Thread類是最簡單的一種實現線程的方式,經過jdk給咱們提供的Thread類,重寫Thread類的run方法便可,那麼當線程啓動的時候,就會執行run方法體的內容。代碼以下:linux

package com.hy.thread.t1;

/**
 * 繼承Thread類的方式實現多線程演示
 * 
 *
 */
public class ThreadDemo extends Thread {

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印當前線程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        td.start(); // 啓動線程

        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印當前線程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

運行結果以下spring

main is running ... 
Thread-0 is running ... 
main is running ... 
Thread-0 is running ... 
Thread-0 is running ... 
main is running ... 
Thread-0 is running ... 
main is running ... 

這裏要注意,在啓動線程的時候,咱們並非調用線程類的run方法,而是調用了線程類的start方法。那麼咱們能不能調用run方法呢?答案是確定的,由於run方法是一個public聲明的方法,所以咱們是能夠調用的,可是若是咱們調用了run方法,那麼這個方法將會做爲一個普通的方法被調用,並不會開啓線程。這裏其實是採用了設計模式中的模板方法模式,Thread類做爲模板,而run方法是在變化的所以放到子類。數據庫

建立多個線程
上面的例子中除了咱們建立的一個線程之外其實還有一個主線程也在執行。除了這兩個線程之外還有沒有其餘的線程在執行了呢,實際上是有的,好比咱們看不到的垃圾回收線程,也在默默的執行。這裏咱們並不去考慮有多少個線程在執行,上面咱們本身建立了一個線程,那麼能不能多建立幾個一塊兒執行呢,答案是確定的。一個Thread類就是一個線程對象,那麼多建立幾個Thread類,並調用其start方法就能夠啓動多個線程了。代碼以下設計模式

package com.hy.thread.t1;

public class MultiThreadDemo extends Thread {

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印當前線程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        
        // 建立四個線程對象,表明四個線程
        MultiThreadDemo td1 = new MultiThreadDemo();
        MultiThreadDemo td2 = new MultiThreadDemo();
        MultiThreadDemo td3 = new MultiThreadDemo();
        MultiThreadDemo td4 = new MultiThreadDemo();
        
        td1.start(); // 啓動線程
        td2.start(); // 啓動線程
        td3.start(); // 啓動線程
        td4.start(); // 啓動線程

        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印當前線程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
}

運行結果以下緩存

main is running ... 
Thread-2 is running ... 
Thread-1 is running ... 
Thread-3 is running ... 
Thread-0 is running ... 
Thread-3 is running ... 
Thread-2 is running ... 
main is running ... 
Thread-1 is running ... 
Thread-0 is running ... 
Thread-1 is running ... 
main is running ... 
Thread-2 is running ... 
Thread-0 is running ... 
Thread-3 is running ... 

咱們發現這裏有個問題,多個線程的名字都是系統定義好的,就是Thread-開頭,後面跟數字,若是咱們每一個線程處理不一樣的任務,那麼咱們能不能給線程起上不一樣的名字,方便咱們排查問題呢?答案是能夠的。只要在建立線程實例的時候,在構造方法中傳入指定的線程名稱便可。以下多線程

package com.hy.thread.t1;

public class MultiThreadDemo extends Thread {

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印當前線程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 指定線程名稱的構造方法
     * 
     * @param name
     */
    public MultiThreadDemo(String name) {
        super(name);
    }
    
    public static void main(String[] args) {
        
        // 建立四個線程對象,表明四個線程
        MultiThreadDemo td1 = new MultiThreadDemo("t1"); // 指定線程的名字
        MultiThreadDemo td2 = new MultiThreadDemo("t2");
        MultiThreadDemo td3 = new MultiThreadDemo("t3");
        MultiThreadDemo td4 = new MultiThreadDemo("t4");
        
        td1.start(); // 啓動線程
        td2.start(); // 啓動線程
        td3.start(); // 啓動線程
        td4.start(); // 啓動線程

        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印當前線程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
}

運行的結果以下:app

main is running ... 
t1 is running ... 
t2 is running ... 
t3 is running ... 
t4 is running ... 
main is running ... 
t1 is running ... 
t2 is running ... 
t4 is running ... 
t3 is running ... 

第二種方式:實現Runnable接口
實現Runnable接口也是一種常見的建立線程的方式。使用接口的方式可讓咱們的程序下降耦合度。Runnable接口中僅僅定義了一個方法,就是run。咱們來看一下Runnable接口的代碼。框架

package java.lang;

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

其實Runnable就是一個線程任務,線程任務和線程的控制分離,這也就是上面所說的解耦。咱們要實現一個線程,能夠藉助Thread類,Thread類要執行的任務就能夠由實現了Runnable接口的類來處理。 這就是Runnable的精髓之所在!less

使用Runnable實現上面的例子步驟以下:

定義一個類實現Runnable接口,做爲線程任務類
重寫run方法,並實現方法體,方法體的代碼就是線程所執行的代碼
定義一個能夠運行的類,並在main方法中建立線程任務類
建立Thread類,並將線程任務類作爲Thread類的構造方法傳入
啓動線程
線程任務類代碼以下

package com.hy.thread.t2;

public class ThreadTarget implements Runnable {

    @Override
    public void run() {
        while(true) {
            System.out.println(Thread.currentThread().getName() + " is running .. ");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

可運行類代碼以下

package com.hy.thread.t2;

public class Main {
    
    public static void main(String[] args) {
        
        ThreadTarget tt = new ThreadTarget(); // 實例化線程任務類
        Thread t = new Thread(tt); // 建立線程對象,並將線程任務類做爲構造方法參數傳入
        t.start(); // 啓動線程
        
        // 主線程的任務,爲了演示多個線程一塊兒執行
        while(true) {
            System.out.println(Thread.currentThread().getName() + " is running .. ");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

線程任務和線程的控制分離,那麼一個線程任務能夠提交給多個線程來執行。這是頗有用的,好比車站的售票窗口,每一個窗口能夠看作是一個線程,他們每一個窗口作的事情都是同樣的,也就是售票。這樣咱們程序在模擬現實的時候就能夠定義一個售票任務,讓多個窗口同時執行這一個任務。那麼若是要改動任務執行計劃,只要修改線程任務類,全部的線程就都會按照修改後的來執行。相比較繼承Thread類的方式來建立線程的方式,實現Runnable接口是更爲經常使用的。

第三種方式:使用內部類的方式
這並非一種新的實現線程的方式,只是另外的一種寫法。好比有些狀況咱們的線程就想執行一次,之後就用不到了。那麼像上面兩種方式都還要再定義一個類,顯得比較麻煩,咱們就能夠經過匿名內部類的方式來實現。使用內部類實現依然有兩種,分別是繼承Thread類和實現Runnable接口。代碼以下:

package com.hy.thread.t3;

public class DemoThread {
    
    public static void main(String[] args) {
        
        // 基於子類的實現
        new Thread() {
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印當前線程的名字
                    try {
                        Thread.sleep(1000); // 休息1000ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();
        
        // 基於接口的實現
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印當前線程的名字
                    try {
                        Thread.sleep(1000); // 休息1000ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        
        // 主線程的方法
        while (true) {
            System.out.println(Thread.currentThread().getName() + " is running ... "); // 打印當前線程的名字
            try {
                Thread.sleep(1000); // 休息1000ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }

}

能夠想象一下,我能不能既基於接口,又基於子類呢?像下面的代碼會執行出什麼樣子呢?

package com.hy.thread.t3;

public class DemoThred2 {
    
    public static void main(String[] args) {
        
        
        new Thread(new Runnable() {
            
            @Override
            public void run() {
                while (true) {
                    System.out.println("runnable is running ... "); // 打印當前線程的名字
                    try {
                        Thread.sleep(1000); // 休息1000ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }) {
            public void run() {
                while (true) {
                    System.out.println("sub is running ... "); // 打印當前線程的名字
                    try {
                        Thread.sleep(1000); // 休息1000ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();
        
        
    }

}

運行結果以下:

sub is running ... 
sub is running ... 
sub is running ... 

咱們能夠看到,實際上是基於子類的執行了,爲何呢,其實很簡單,咱們先來看一下爲何不基於子類的時候Runnable的run方法能夠執行。這個要從Thread的源碼看起,下面是我截取的代碼片斷。   

public Thread(Runnable target)
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        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);
        if (inheritThreadLocals && 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();
    }

其實上面的衆多代碼就是爲了表現 this.target = target 那麼target是什麼呢,是Thread類的成員變量。那麼在什麼地方用到了target呢?下面是run方法的內容。

@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }


咱們能夠看到,若是經過上面的構造方法傳入target,那麼就會執行target中的run方法。可能有朋友就會問了,咱們同時繼承Thread類和實現Runnable接口,target不爲空,那麼爲什麼不執行target的run呢。不要忘記了,咱們在子類中已經重寫了Thread類的run方法,所以run方法已經不在是咱們看到的這樣了。那固然也就不回執行target的run方法。

第四種方式:定時器
定時器能夠說是一種基於線程的一個工具類。能夠定時的來執行某個任務。好比要在凌晨的時候彙總一些數據,好比要每隔10分鐘抓取一次某個網站上的數據等等,總之計時器無處不在。咱們通常將須要定時完成的任務稱之爲計劃任務,這在不少的系統中是很是常見的,好比linux的計劃任務,好比Windows下的任務計劃等等。咱們本身的系統中也須要不少定時執行的也都須要計劃任務。最簡單的計劃任務就能夠經過jdk給我提供的API來實現,固然也有不少的計劃任務的框架,好比spring的schedule以及著名的quartz。咱們這裏不去討論其餘的計劃任務框架,咱們就來看一下jdk所給咱們提供的API來實現定時任務。例1:在2017年10月11日晚上10點執行任務。

package com.roocon.thread.t3;

import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 定時器舉例
 * 
 */
public class TimerDemo {

    private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    public static void main(String[] args) throws Exception {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("定時任務執行了....");
            }
        }, format.parse("2017-10-11 22:00:00"));
    }

}

例2: 每隔5s執行一次

package com.roocon.thread.t3;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo2 {
    
    public static void main(String[] args) {
        Timer timer = new Timer();
        
        timer.schedule(new TimerTask() {
            
            @Override
            public void run() {
                System.out.println("Hello");
            }
        }, new Date(), 5000);
    }

}


關於Spring的定時任務,能夠經過spring的教程來學習。

第五種方式:帶返回值的線程實現方式
咱們發現上面提到的不論是繼承Thread類仍是實現Runnable接口,發現有兩個問題,第一個是沒法拋出更多的異常,第二個是線程執行完畢以後並沒有法得到線程的返回值。那麼下面的這種實現方式就能夠完成咱們的需求。這種方式的實現就是咱們後面要詳細介紹的Future模式,只是在jdk5的時候,官方給咱們提供了可用的API,咱們能夠直接使用。可是使用這種方式建立線程比上面兩種方式要複雜一些,步驟以下。

1. 建立一個類實現Callable接口,實現call方法。這個接口相似於Runnable接口,但比Runnable接口更增強大,增長了異常和返回值。
2. 建立一個FutureTask,指定Callable對象,作爲線程任務。
3. 建立線程,指定線程任務。
4. 啓動線程

代碼以下:

package com.roocon.thread.t4;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class CallableTest {
    
    public static void main(String[] args) throws Exception {
        Callable<Integer> call = new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                System.out.println("thread start .. ");
                Thread.sleep(2000);
                return 1;
            }
        };
        
        FutureTask<Integer> task = new FutureTask<>(call);
        Thread t =  new Thread(task);
        
        t.start();
        System.out.println("do other thing .. ");
        System.out.println("拿到線程的執行結果 : " + task.get());
    }

}


執行結果以下:

do other thing .. 
thread start .. 
拿到線程的執行結果 : 1

Callable中能夠經過範型參數來指定線程的返回值類型。經過FutureTask的get方法拿到線程的返回值。

第六種方式:基於線程池的方式
咱們知道,線程和數據庫鏈接這些資源都是很是寶貴的資源。那麼每次須要的時候建立,不須要的時候銷燬,是很是浪費資源的。那麼咱們就可使用緩存的策略,也就是使用線程池。固然了,線程池也不須要咱們來實現,jdk的官方也給咱們提供了API。

代碼以下:

package com.roocon.thread.t5;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    
    public static void main(String[] args) {
        
        // 建立線程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        
        while(true) {
            threadPool.execute(new Runnable() { // 提交多個線程任務,並執行
                
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " is running ..");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

}

執行結果以下:

pool-1-thread-4 is running ..
pool-1-thread-1 is running ..
pool-1-thread-6 is running ..
pool-1-thread-2 is running ..
pool-1-thread-8 is running ..
pool-1-thread-3 is running ..
pool-1-thread-5 is running ..
pool-1-thread-9 is running ..
pool-1-thread-10 is running ..
pool-1-thread-7 is running ..

線程池的內容還有很是多,這裏再也不詳細地講解。

Spring方式:使用Spring來實現多線程 這種方式依賴於Spring3以上版本,咱們能夠經過Spring的@Async註解很是方便的實現多線程。  

相關文章
相關標籤/搜索