您好,登錄后才能下訂單哦!
今天小編給大家分享一下java實現多線程的方式是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
實現多線程的方式:1、繼承Thread類,通過JDK提供的Thread類,重寫Thread類的run方法即可;2、實現Runnable接口,Runnable是一個“@FunctionalInterface”函數式接口,也就意味了可以利用JDK8提供的lambda的方式來創建線程任務;3、使用內部類的方式;4、利用定時器;5、帶返回值的線程實現方式;6、基于線程池實現多線程。
多線程的形式上實現方式主要有兩種,一種是繼承Thread類,一種是實現Runnable接口。本質上實現方式都是來實現線程任務,然后啟動線程執行線程任務(這里的線程任務實際上就是run方法)。這里所說的6種,實際上都是在以上兩種的基礎上的一些變形。
下面分別就這6中實現方式一一介紹。
第一種方式:繼承Thread類
萬物皆對象,那么線程也是對象,對象就應該能夠抽取其公共特性封裝成為類,使用類可以實例化多個對象,那么實現線程的第一種方式就是繼承Thread類。繼承Thread類是最簡單的一種實現線程的方式,通過JDK提供的Thread類,重寫Thread類的run方法即可,那么當線程啟動的時候,就會執行run方法體的內容。代碼如下:
package com.kingh.thread.create;
/**
* 繼承Thread類的方式創建線程
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/13 19:19
*/
public class CreateThreadDemo1 extends Thread {
public CreateThreadDemo1() {
// 設置當前線程的名字
this.setName("MyThread");
}
@Override
public void run() {
// 每隔1s中輸出一次當前線程的名字
while (true) {
// 輸出線程的名字,與主線程名稱相區分
printThreadInfo();
try {
// 線程休眠一秒
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws Exception {
// 注意這里,要調用start方法才能啟動線程,不能調用run方法
new CreateThreadDemo1().start();
// 演示主線程繼續向下執行
while (true) {
printThreadInfo();
Thread.sleep(1000);
}
}
/**
* 輸出當前線程的信息
*/
private static void printThreadInfo() {
System.out.println("當前運行的線程名為: " + Thread.currentThread().getName());
}
}
運行結果如下
當前運行的線程名為: main
當前運行的線程名為: MyThread
當前運行的線程名為: main
當前運行的線程名為: MyThread
當前運行的線程名為: MyThread
當前運行的線程名為: main
這里要注意,在啟動線程的時候,并不是調用線程類的run方法,而是調用了線程類的start方法。那么我們能不能調用run方法呢?答案是肯定的,因為run方法是一個public聲明的方法,因此我們是可以調用的,但是如果我們調用了run方法,那么這個方法將會作為一個普通的方法被調用,并不會開啟線程。這里實際上是采用了設計模式中的模板方法模式,Thread類作為模板,而run方法是在變化的,因此放到子類來實現。
上面的例子中除了我們創建的一個線程以外其實還有一個主線程也在執行。那么除了這兩個線程以外還有沒有其他的線程在執行了呢,其實是有的,比如我們看不到的垃圾回收線程,也在默默的執行。這里我們并不去考慮有多少個線程在執行,上面我們自己創建了一個線程,那么能不能多創建幾個一起執行呢,答案是肯定的。一個Thread類就是一個線程對象,那么多創建幾個Thread類,并調用其start方法就可以啟動多個線程了。代碼如下
package com.kingh.thread.create;
/**
* 創建多個線程同時執行
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/18 9:46
*/
public class CreateMultiThreadDemo2 extends Thread {
public CreateMultiThreadDemo2(String name) {
// 設置當前線程的名字
this.setName(name);
}
@Override
public void run() {
// 每隔1s中輸出一次當前線程的名字
while (true) {
// 輸出線程的名字,與主線程名稱相區分
printThreadInfo();
try {
// 線程休眠一秒
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws Exception {
// 注意這里,要調用start方法才能啟動線程,不能調用run方法
new CreateMultiThreadDemo2("MyThread-01").start();
// 創建多個線程實例,同時執行
new CreateMultiThreadDemo2("MyThread-02").start();
// 演示主線程繼續向下執行
while (true) {
printThreadInfo();
Thread.sleep(1000);
}
}
/**
* 輸出當前線程的信息
*/
private static void printThreadInfo() {
System.out.println("當前運行的線程名為: " + Thread.currentThread().getName());
}
}
運行結果如下
當前運行的線程名為: main
當前運行的線程名為: MyThread-02
當前運行的線程名為: MyThread-01
當前運行的線程名為: main
當前運行的線程名為: MyThread-01
當前運行的線程名為: MyThread-02
當前運行的線程名為: main
可以看到,通過創建多個Thread類,并且調用其start方法,啟動了多個線程。每個線程都有自己的名字,在上述代碼中,分別給創建的線程指定了MyThread-01和MyThread-02這個名字,然后構造方法中通過調用父類的setName方法給線程名字賦值。如果不指定線程名字,系統會默認指定線程名,命名規則是Thread-N的形式。但是為了排查問題方便,建議在創建線程的時候指定一個合理的線程名字。下面的代碼是不使用線程名的樣子
package com.kingh.thread.create;
/**
* 創建多個線程同時執行,使用系統默認線程名
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/18 9:46
*/
public class CreateMultiThreadDemo3 extends Thread {
@Override
public void run() {
// 每隔1s中輸出一次當前線程的名字
while (true) {
// 輸出線程的名字,與主線程名稱相區分
printThreadInfo();
try {
// 線程休眠一秒
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) throws Exception {
// 注意這里,要調用start方法才能啟動線程,不能調用run方法
new CreateMultiThreadDemo3().start();
// 創建多個線程實例,同時執行
new CreateMultiThreadDemo3().start();
// 演示主線程繼續向下執行
while (true) {
printThreadInfo();
Thread.sleep(1000);
}
}
/**
* 輸出當前線程的信息
*/
private static void printThreadInfo() {
System.out.println("當前運行的線程名為: " + Thread.currentThread().getName());
}
}
運行的結果如下:
當前運行的線程名為: main
當前運行的線程名為: Thread-1
當前運行的線程名為: Thread-0
當前運行的線程名為: main
當前運行的線程名為: Thread-1
當前運行的線程名為: Thread-0
第二種方式:實現Runnable接口
實現Runnable接口也是一種常見的創建線程的方式,使用接口的方式可以讓我們的程序降低耦合度。Runnable接口中僅僅定義了一個方法,就是run。我們來看一下Runnable接口的代碼。
package java.lang;
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
其實Runnable就是一個線程任務,線程任務和線程的控制分離,這也就是上面所說的解耦。我們要實現一個線程,可以借助Thread類,Thread類要執行的任務就可以由實現了Runnable接口的類來處理。 這就是Runnable的精髓之所在!
Runnable 是一個@FunctionalInterface 函數式接口,也就意味了可以利用JDK8提供的lambda的方式來創建線程任務,后面的代碼中會給讀者演示具體如何使用。
使用Runnable實現上面的例子步驟如下:
定義一個類實現Runnable接口,作為線程任務類
重寫run方法,并實現方法體,方法體的代碼就是線程所執行的代碼
定義一個可以運行的類,并在main方法中創建線程任務類
創建Thread類,并將線程任務類做為Thread類的構造方法傳入
啟動線程
線程任務就是線程要做的事情,這里我們讓這個線程每隔1s中打印自己的名字
package com.kingh.thread.create;
/**
* 線程任務
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/18 10:04
*/
public class CreateThreadDemo4_Task implements Runnable {
@Override
public void run() {
// 每隔1s中輸出一次當前線程的名字
while (true) {
// 輸出線程的名字,與主線程名稱相區分
printThreadInfo();
try {
// 線程休眠一秒
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* 輸出當前線程的信息
*/
private static void printThreadInfo() {
System.out.println("當前運行的線程名為: " + Thread.currentThread().getName());
}
}
在這里創建線程,并把任務交給線程處理,然后啟動線程。
package com.kingh.thread.create;
/**
* 創建線程
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/18 10:04
*/
public class CreateThreadDemo4_Main {
public static void main(String[] args) throws Exception {
// 實例化線程任務類
CreateThreadDemo4_Task task = new CreateThreadDemo4_Task();
// 創建線程對象,并將線程任務類作為構造方法參數傳入
new Thread(task).start();
// 主線程的任務,為了演示多個線程一起執行
while (true) {
printThreadInfo();
Thread.sleep(1000);
}
}
/**
* 輸出當前線程的信息
*/
private static void printThreadInfo() {
System.out.println("當前運行的線程名為: " + Thread.currentThread().getName());
}
}
線程任務和線程的控制分離,那么一個線程任務可以提交給多個線程來執行。這是很有用的,比如車站的售票窗口,每個窗口可以看做是一個線程,他們每個窗口做的事情都是一樣的,也就是售票。這樣我們程序在模擬現實的時候就可以定義一個售票任務,讓多個窗口同時執行這一個任務。那么如果要改動任務執行計劃,只要修改線程任務類,所有的線程就都會按照修改后的來執行。相比較繼承Thread類的方式來創建線程的方式,實現Runnable接口是更為常用的。
這里就是為了簡化內部類的編寫,簡化了大量的模板代碼,顯得更加簡潔。如果讀者看不明白,可以讀完內部類方式之后,回過來再看這段代碼。
package com.kingh.thread.create;
/**
* 創建線程with lambda
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/18 10:04
*/
public class CreateThreadDemo5_Lambda {
public static void main(String[] args) throws Exception {
// 使用lambda的形式實例化線程任務類
Runnable task = () -> {
while (true) {
// 輸出線程的名字
printThreadInfo();
}
};
// 創建線程對象,并將線程任務類作為構造方法參數傳入
new Thread(task).start();
// 主線程的任務,為了演示多個線程一起執行
while (true) {
printThreadInfo();
Thread.sleep(1000);
}
}
/**
* 輸出當前線程的信息
*/
private static void printThreadInfo() {
System.out.println("當前運行的線程名為: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
第三種方式:使用內部類的方式
這并不是一種新的實現線程的方式,只是另外的一種寫法。比如有些情況我們的線程就想執行一次,以后就用不到了。那么像上面兩種方式(繼承Thread類和實現Runnable接口)都還要再定義一個類,顯得比較麻煩,我們就可以通過匿名內部類的方式來實現。使用內部類實現依然有兩種,分別是繼承Thread類和實現Runnable接口。代碼如下:
package com.kingh.thread.create;
/**
* 匿名內部類的方式創建線程
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/18 10:04
*/
public class CreateThreadDemo6_Anonymous {
public static void main(String[] args) {
// 基于子類的方式
new Thread() {
@Override
public void run() {
while (true) {
printThreadInfo();
}
}
}.start();
// 基于接口的實現
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
printThreadInfo();
}
}
}).start();
}
/**
* 輸出當前線程的信息
*/
private static void printThreadInfo() {
System.out.println("當前運行的線程名為: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
可以想象一下,我能不能既基于接口,又基于子類呢?像下面的代碼會執行出什么樣子呢?
package com.kingh.thread.create;
/**
* 匿名內部類的方式創建線程
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/18 10:04
*/
public class CreateThreadDemo7_Anonymous {
public static void main(String[] args) {
// 基于子類和接口的方式
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
printInfo("interface");
}
}
}) {
@Override
public void run() {
while (true) {
printInfo("sub class");
}
}
}.start();
}
/**
* 輸出當前線程的信息
*/
private static void printInfo(String text) {
System.out.println(text);
try {
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
運行結果如下:
sub class
sub class
我們可以看到,其實是基于子類的執行了,為什么呢,其實很簡單,我們先來看一下為什么不基于子類的時候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方法。
剛才使用匿名內部類,會發現代碼還是比較冗余的,lambda可以大大簡化代碼的編寫。用lambda來改寫上面的基于接口的形式的代碼,如下
// 使用lambda的形式
new Thread(() -> {
while (true) {
printThreadInfo();
}
}).start();
// 對比不使用lambda的形式
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
printThreadInfo();
}
}
}).start();
第四種方式:定時器
定時器可以說是一種基于線程的一個工具類,可以定時的來執行某個任務。在應用中經常需要定期執行一些操作,比如要在凌晨的時候匯總一些數據,比如要每隔10分鐘抓取一次某個網站上的數據等等,總之計時器無處不在。
在Java中實現定時任務有很多種方式,JDK提供了Timer類來幫助開發者創建定時任務,另外也有很多的第三方框架提供了對定時任務的支持,比如Spring的schedule以及著名的quartz等等。因為Spring和quartz實現都比較重,依賴其他的包,上手稍微有些難度,不在本篇博客的討論范圍之內,這里就看一下JDK所給我們提供的API來實現定時任務。
package com.kingh.thread.create;
import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;
/**
* 定時任務
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/18 10:04
*/
public class CreateThreadDemo9_Timer {
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"));
}
}
package com.kingh.thread.create;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* 定時任務
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/18 10:04
*/
public class CreateThreadDemo10_Timer {
public static void main(String[] args){
// 創建定時器
Timer timer = new Timer();
// 提交計劃任務
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定時任務執行了...");
}
},
new Date(), 1000);
}
}
關于Spring的定時任務,可以參考 《Spring計劃任務》
第五種方式:帶返回值的線程實現方式
我們發現上面提到的不管是繼承Thread類還是實現Runnable接口,發現有兩個問題,第一個是無法拋出更多的異常,第二個是線程執行完畢之后并無法獲得線程的返回值。那么下面的這種實現方式就可以完成我們的需求。這種方式的實現就是我們后面要詳細介紹的Future模式,只是在jdk5的時候,官方給我們提供了可用的API,我們可以直接使用。但是使用這種方式創建線程比上面兩種方式要復雜一些,步驟如下。
創建一個類實現Callable接口,實現call方法。這個接口類似于Runnable接口,但比Runnable接口更加強大,增加了異常和返回值。
創建一個FutureTask,指定Callable對象,做為線程任務。
創建線程,指定線程任務。
啟動線程
代碼如下:
package com.kingh.thread.create;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 帶返回值的方式
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/18 10:04
*/
public class CreateThreadDemo11_Callable {
public static void main(String[] args) throws Exception {
// 創建線程任務
Callable<Integer> call = () -> {
System.out.println("線程任務開始執行了....");
Thread.sleep(2000);
return 1;
};
// 將任務封裝為FutureTask
FutureTask<Integer> task = new FutureTask<>(call);
// 開啟線程,執行線程任務
new Thread(task).start();
// ====================
// 這里是在線程啟動之后,線程結果返回之前
System.out.println("這里可以為所欲為....");
// ====================
// 為所欲為完畢之后,拿到線程的執行結果
Integer result = task.get();
System.out.println("主線程中拿到異步任務執行的結果為:" + result);
}
}
執行結果如下:
這里可以為所欲為....
線程任務開始執行了....
主線程中拿到異步任務執行的結果為:1
Callable中可以通過范型參數來指定線程的返回值類型。通過FutureTask的get方法拿到線程的返回值。
第六種方式:基于線程池的方式
我們知道,線程和數據庫連接這些資源都是非常寶貴的資源。那么每次需要的時候創建,不需要的時候銷毀,是非常浪費資源的。那么我們就可以使用緩存的策略,也就是使用線程池。當然了,線程池也不需要我們來實現,jdk的官方也給我們提供了API。
代碼如下:
package com.kingh.thread.create;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 線程池
*
* @author <a href="https://blog.csdn.net/king_kgh>Kingh</a>
* @version 1.0
* @date 2019/3/18 10:04
*/
public class CreateThreadDemo12_ThreadPool {
public static void main(String[] args) throws Exception {
// 創建固定大小的線程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while (true) {
// 提交多個線程任務,并執行
threadPool.execute(new Runnable() {
@Override
public void run() {
printThreadInfo();
}
});
}
}
/**
* 輸出當前線程的信息
*/
private static void printThreadInfo() {
System.out.println("當前運行的線程名為: " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
執行結果如下:
當前運行的線程名為: pool-1-thread-1
當前運行的線程名為: pool-1-thread-2
當前運行的線程名為: pool-1-thread-4
當前運行的線程名為: pool-1-thread-3
當前運行的線程名為: pool-1-thread-7
當前運行的線程名為: pool-1-thread-8
當前運行的線程名為: pool-1-thread-9
當前運行的線程名為: pool-1-thread-6
當前運行的線程名為: pool-1-thread-5
當前運行的線程名為: pool-1-thread-10
以上就是“java實現多線程的方式是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。