創建一個空線程
public class EmptyThreadDemo {
public static void main(String[] args) {
//使用Thread類創建和啟動線程
Thread thread = new Thread();
Print.tco("線程名稱:"+thread.getName());
Print.tco("線程Id:"+thread.getId());
Print.tco("線程狀態:"+thread.getState());
Print.tco("線程優先級"+thread.getPriority());
Print.tco(Thread.currentThread().getName()+"運行結束");
thread.start();
}
}
首先創建一個空線程,通過該線程在堆內存的引用地址獲取到該線程的名稱,ID,狀態,優先級。
此時線程並沒有啟動,其線程狀態是New。然后用thread.start()啟動該線程,線程會去執行用戶代碼邏輯塊,邏輯塊的入口是run()方法,我們可以看看run方法的源碼:
public void run() {
if (target != null) {
target.run();
}
}
target是Thread類中的一個實例屬性,它是這樣定義的。
private Runnable target;
它是一個Runnable類型的屬性,Runnable是一個接口類,里面有定義一個方法便是run(),這也意味着在新線程啟動后,會以run()方法為代碼邏輯塊入口執行用戶代碼,而內部進一步調用了target目標實例執行類的run()方法,而我們並沒有去實現這個方法,所以什么都沒有執行,該線程也稱空線程結束了,整個JVM進程也結束。
工具類
這些工具類的方法后續會用上,便於編碼。
public class ThreadUtil{
public static String getCurThreadName(){
return Thread.currentThread().getName();
}
public static void sleepMillSeconds(int millsecond){
LockSupport.parkNanos(millsecond*100L*100L);
}
public static void execute(String cfo){
synchronized(System.out){
System.out.println(cfo);
}
}
}
public class Print{
public static void tco(Object s){
String cfo = "["+ThreadUtil.getCurThreadName()+"]"+s;
ThreadUtil.execute(cfo);
}
}
通過繼承Thread類的方式創建線程目標類
前面的例子向我們說明了線程start之后,如何執行用戶定義的線程代碼邏輯。因此我們想要線程去執行我們的代碼就主要有兩種方式:
- 繼承Thread類去重寫run()方法。
- 實現Runnable接口的run()方法,並將實現好的接口的實現類以構造參數的形式傳入Thread的target實例屬性中。
接下來我們來以代碼詮釋第一種方式
public class CreateDemo{
private static final int MAX = 5;
private static int treadNo = 1;
static class DemoThread extends Thread{
public DemoThread(){
//調用父類的構造方法
super("DemoThread-"+treadNo++);
}
@Override
public void run(){
for(int i = 0;i < MAX;i++){
Print.tco(getName()+", 輪次為:"+i);
}
Print.tco(getName()+" 運行結束.");
}
public static void main(String[] args){
Thread thread = null;
for(int i = 0;i < 2;i++){
thread = new DemoThread();
thread.start();
}
Print.tco(getCurThreadName()+" 運行結束.");
}
}
}
例子中,我們建了一個靜態內部類去繼承Thread類,調用其帶String的構造方法構造該實現類,重寫Thread類的run()方法,添加屬於我們的邏輯代碼塊。
這里的代碼邏輯是循環5次,每次輸出當前運行線程的名字以及輪次。
至於為什么是靜態內部類,主要是為了方便調用外部類的屬性,而如果該內部類不是靜態的話還需要new外部類才new當前內部類。當然將其寫為外部類,依然不影響后面的輸出結果。
通過實現Runnable接口創建target執行目標類來創建線程目標類
在我們用代碼演示之前,我們可以來看一下Thread的構造方法有哪些?
圖中我們可以看到,Thread給我們提供了樣式豐富的構造方法,其中有Runnable的也居多。因此我們可以以Runnable為構造參數的形式給Thread實例類傳入target實例屬性。
構造參數String類型實則為所創建線程的名稱。
接着我們來用代碼真正實現
public class CreateDemo2 {
public static final int MAX = 5;
static int threadNo = 1;
static class RunTarget implements Runnable{
@Override
public void run() {
String name = getCurThreadName();
for (int i = 0; i < MAX; i++) {
Print.tco(name+",輪次:"+i);
}
Print.tco(name+" 運行結束.");
}
}
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
Thread thread = new Thread(new RunTarget(),"RunnableThread-"+threadNo++);
thread.start();
}
Print.tco(getCurThreadName()+",運行完畢.");
}
}
這里我們可以看到我們實現了Rnnable接口的run()方法,將這個target目標執行類以構造參數的形式傳入了我們所創建Thread實例類,當start()的時候,JVM就會啟動線程運行用戶邏輯代碼,也就是我們實現Runnable接口run()方法的邏輯代碼。
通過匿名類來創建Runnable線程目標類
通過優雅的實現方式來創建Runnable線程目標類
public class CreateDemo2_2 {
public static final int MAX = 5;
static int threadNo = 1;
public static void main(String[] args) {
Thread thread = null;
for (int i = 0; i < 2; i++) {
thread = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < MAX; j++) {
Print.tco(getCurThreadName()+",輪次:"+j);
}
Print.tco(getCurThreadName()+" 運行結束.");
}
},"RunnableThread-"+threadNo++);
thread.start();
}
Print.tco(getCurThreadName()+" 運行結束.");
}
}
通過Lambda表達式創建Runnable線程目標類
通過優雅的實現方式來創建Runnable線程目標類
public class CreateDemo2_3{
private static final int MAX = 5;
private static int treadNo = 1;
public static void main(String[] args){
Thread tread = null;
for(int i = 0;i < 2;i++){
tread = new Thread(()->{
for(int j = 0;j < MAX;j++){
Print.tco(getCurThreadName()+",輪次為:"+j);
}
Print.tco(getCurThreadName()+" 運行結束.");
},"RunnableThread-"+treadNo++);
thread.start();
}
Print.tco(getCurThreadName()+" 運行結束.");
}
}
繼承Thread類來創建線程目標類和通過實現Runnable接口創建線程目標類有什么不同嗎?
- 第一種方式創建線程目標類,由於每次創建類的內存地址都是不一樣的,因此每個數據資源的內存地址都是不一樣的,所以每個線程目標類都有其唯一的數據資源,在執行線程時,只是對着自己的數據資源進行業務處理,不會影響其他線程的數據資源。
-- 第一種方式創建線程目標類的優點:由於是繼承了Thread類,其子類便享有父類的getName()、getID()、getStatus()等方法,可以很輕松的訪問當前線程的各種信息狀態和對當前線程進行操作。
-- 第一種方式的缺點: 由於一個類僅僅只能繼承一個父類(不包括接口),所以在當前類繼承了其他父類時,便用不了以繼承Thread的方式來創建線程目標類了。 - 第二種方式以實現Runnable接口的方式得到target目標類,在用這個target目標類以構造參數的形式傳入Thread實例中,得以創建真正的線程。這里我們可以發現多個線程用的target目標執行實現類都是用的同一個引用地址,也即多個線程使用的數據資源都是同一個。也就是說使用實現Runnable接口來創建線程目標類,其多個線程業務邏輯並行處理同一個數據資源。
-- 第二種方式創建線程目標類的優點:更好地體現了面向對象的設計思想。通過實現Runnable接口的方式設計多個target執行目標執行類可以更加方便,清晰地執行邏輯和數據存儲的分離。
-- 第二種方式創建線程目標類的缺點:由於數據資源是被多個線程共享的,所以對數據資源做共享操作的時候會出現線程安全的問題。而且由於target目標類不是繼承Thread的,所以要得到當前線程的信息,只能以Thread.currentThread()來獲取當前在cpu時間片運行的線程來獲取信息。
通過創建FutureTask和實現Callable接口來創建線程目標類
前面的兩種方式其實都有一個共同的缺陷:由於run()方法的返回值類型是void類型,我們在線程異步執行完成之后是拿不到線程執行完成后的結果,很多時候我們想要了解線程異步執行的時候的狀態,結果,前面的兩種方式並不足以滿足我們的需求。
於是為了解決這個問題,在JDK1.5版本提供了一種新的多線程創建方法:便是使用Callable接口和FutureTask相結合來創建線程目標類。
首先我們先從Callable接口的源碼定義來認識一下它
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
我們從源碼認識到,Callable是一個函數式接口,其中有唯一方法為call()方法,其方法的返回值是Callable接口的泛型形參,方法還有一個Exception的異常聲明,容許方法的實現可以有異常拋出,且不做捕獲。
不難看出call()方法的功能比run()方法要豐富多了,它多了返回值,對了異常的聲明,功能十分強大。但是其能代替Runnable實例作為Thread的target執行類嗎?顯然這是不能的,上文我們提到target實例的屬性是Runnable,而其是Callable類型的,所以並不能作為target來運行。
那么我們要通過何種方式來讓線程啟動的時候,進入run()方法里面運行的是call()方法里的代碼邏輯塊呢?
接下來我們來認識一下RunnableFuture接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
我們可以看到RunnableFuture接口繼承了Runnable接口,使其實現類可以作為target目標類,同時它還繼承了Future接口,那么這個接口賦予了RunnableFuture什么接口方法呢?我們來查看一下Future接口。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
通過查看其實現我們可以知道Future接口賦予了RunnableFuture五個接口方法,分別是:
- cancel():取消異步任務的執行。
- isCancelled():查看異步任務是否取消了。
- isDone():查看異步任務是否執行完成。
- get():阻塞性獲取異步任務的執行的結果。
- get(long timeout,TimeUnit unit):限時的阻塞性獲取異步任務的執行的結果。
那么此時RunnableFuture接口就擁有了可以作為target實現類,可以獲取線程的執行結果,執行狀態的方法。那么最后就要實現該接口了,JDK已經幫我們實現好了,其名字叫做FutureTask
此時的FutureTask既能作為一個Runnable類型的target執行目標直接被Thread執行,有擁有着可以獲取Callable執行結果,執行狀態的能力。那么FutureTask是如何和Callable聯系上的呢?我們可以查看FutureTask,其中有一個實例屬性:
private Callable<V> callable;
其屬性是用來保存並發執行的Callable類型的任務的,我們再來看看Future實現run方法的內部代碼
public void run() {
if (state != NEW ||
!RUNNER.compareAndSet(this, 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);
}
}
protected void set(V v) {
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = v;
STATE.setRelease(this, NORMAL); // final state
finishCompletion();
}
}
此時我們恍然大悟,在run()方法中調用了Callable的call()方法,並將方法的返回值"set"起來了,那么它保存在哪呢?它是保存在屬性outcome中,方便get()的獲取。
最終我們可以捋一下Callable接口和FaskTask是怎么創建線程目標類的。
於是該線程的執行流程便是:
- 首先線程start(),JVM會啟動線程執行用戶代碼邏輯塊,代碼邏輯塊的入口是run(),而run方法中調用了target執行類的run()方法,此時這個target便是我們已構造參數形式傳入到Thread中的FutureTask,調用其run()方法,里面又調用了callable.call()方法,執行結果會保存在屬性outcome中,靜待調用線程調用。
我們用一個例子簡單展現一下
public class CreateDemo3 {
public static final int MAX_TURN = 5;
public static final int COMPUTE_TIMES = 100000000;
static class ReturnableTask implements Callable<Long>{
@Override
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
Print.tco(getCurThreadName()+" 線程開始運行.");
Thread.sleep(1000);
for (int i = 0; i < COMPUTE_TIMES; i++) {
int j = i * 10000;
}
long used = System.currentTimeMillis()-startTime;
Print.tco(getCurThreadName()+" 線程運行結束.");
return used;
}
}
public static void main(String[] args) throws InterruptedException {
ReturnableTask task = new ReturnableTask();
FutureTask<Long> futureTask = new FutureTask<Long>(task);
Thread thread = new Thread(futureTask,"returnableThread");
thread.start();
Thread.sleep(500);
System.out.println(getCurThreadName()+" 讓子彈飛一會兒");
System.out.println(getCurThreadName()+" 做一點自己的事情");
for (int i = 0; i < COMPUTE_TIMES; i++) {
int j = i * 10000;
}
System.out.println(ThreadUtil.getCurThreadName()+" 獲取並發任務執行結果.");
try {
System.out.println(thread.getName()+" 線程占用時間:"+futureTask.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(ThreadUtil.getCurThreadName()+" 運行結束.");
}
}
通過線程池來創建線程目標類
前面的許多例子所創建的Thread實例類都在執行完成之后銷毀了,這些線程實例都是不可復用的。實際上線程的創建,銷毀在時間成本上,資源成本(因為線程創建需要JVM分配棧內存等)上耗費都很高,在高並發的場景下,斷然不能頻繁的進行線程的創建和銷毀,需要的是線程的可復用性。此時需要的是池技術,JAVA中提供了一個靜態工廠來創建不同的線程池,該靜態工廠為Executors工廠類。
接下來我們使用一個例子來實現線程池,以及線程的調度執行
/**
* 第四種方式創建線程類:通過線程池創建線程
*/
public class CreateDemo4 {
public static final int MAX = 5;
//創建一個包含三個線程的線程池
private static ExecutorService pool = Executors.newFixedThreadPool(3);
static class DemoThread implements Runnable{
@Override
public void run() {
for (int i = 1; i <= MAX; i++) {
Print.tco(ThreadUtil.getCurThreadName()+",DemoThread輪次:"+i);
ThreadUtil.sleepMilliSeconds(10);
}
}
}
static class ReturnableTask implements Callable<Long>{
//返回並發執行的時間
@Override
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
Print.tco(ThreadUtil.getCurThreadName()+" 線程運行開始.");
for (int i = 1; i <= MAX; i++) {
Print.tco(ThreadUtil.getCurThreadName()+",Callable輪次:"+i);
ThreadUtil.sleepMilliSeconds(10);
}
long used = System.currentTimeMillis() - startTime;
Print.tco(ThreadUtil.getCurThreadName()+" 線程運行結束");
return used;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
pool.execute(new DemoThread());//執行線程實例,無返回
pool.execute(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= MAX; i++) {
Print.tco(ThreadUtil.getCurThreadName()+",Runnable輪次:"+i);
ThreadUtil.sleepMilliSeconds(10);
}
}
});
Future<Long> submit = pool.submit(new ReturnableTask());
Long res = submit.get();
System.out.println("異步任務的執行結果為:"+res);
Thread.sleep(10);
System.out.println(ThreadUtil.getCurThreadName()+" 線程結束.");
}
}