1. 程式人生 > >java多執行緒的6種實現方式詳解

java多執行緒的6種實現方式詳解

多執行緒的形式上實現方式主要有兩種,一種是繼承Thread類,一種是實現Runnable介面。本質上實現方式都是來實現執行緒任務,然後啟動執行緒執行執行緒任務(這裡的執行緒任務實際上就是run方法)。這裡所說的6種,實際上都是在以上兩種的基礎上的一些變形。 繼承Thread類 萬物皆物件,那麼執行緒也是物件,物件就應該能夠抽取其公共特性封裝成為類,使用類可以例項化多個物件,那麼實現執行緒的第一種方式就是繼承Thread類的方式。繼承Thread類是最簡單的一種實現執行緒的方式,通過jdk給我們提供的Thread類,重寫Thread類的run方法即可,那麼當執行緒啟動的時候,就會執行run方法體的內容。程式碼如下: package com.hy.thread.t1;

/**

  • 繼承Thread類的方式實現多執行緒演示
  • @author 007

*/ 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();
        }
    }
}

}

執行結果如下 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 …

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();
        }
    }
}

}

執行的結果如下: 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的精髓之所在! 使用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 … 3 我們可以看到,其實是基於子類的執行了,為什麼呢,其實很簡單,我們先來看一下為什麼不基於子類的時候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來實現定時任務。 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 …