Java多執行緒的實現
用多執行緒只有一個目的:更好的利用cpu資源.燒水的例子.(當洗杯子花5分鐘,執行緒要停5分鐘等待返回結果才能進行後續的燒水操作,新開一個執行緒執行洗杯子操作)。
一、關於執行緒的一些概念
- cpu時間片:我們作業系統看起來可以多個程式同時執行.分時作業系統,將時間分成長短相同的時間區域,分配給一個執行緒使用,當執行緒還沒有結束,時間片已經過去,該執行緒只有先停止,等待下一個時間片.cpu執行很快,中間的停頓感覺不出來.
- 多執行緒:指的是這個程式(一個程式)執行時產生了不止一個執行緒(比如,下載程式,開啟多個執行緒同時進行.)
- 並行:多個cpu例項或者多臺機器同時執行一段處理邏輯,是真正的同時。
- 併發:通過cpu排程演算法,讓使用者看上去同時執行,實際上從cpu操作層面不是真正的同時。併發往往在場景中有公用的資源,那麼針對這個公用的資源往往產生瓶頸,我們會用TPS或者QPS來反應這個系統的處理能力.
- 執行緒安全:經常用來描繪一段程式碼。指在併發的情況之下,該程式碼經過多執行緒使用,執行緒的排程順序不影響任何結果。這個時候使用多執行緒,我們只需要關注系統的記憶體,cpu是不是夠用即可。反過來,執行緒不安全就意味著執行緒的排程順序會影響最終結果.
- Java中的同步指的是通過人為的控制和排程,保證共享資源的多執行緒訪問成為執行緒安全,來保證結果的準確。如上面的程式碼簡單加入@synchronized關鍵字。在保證結果準確的同時,提高效能,才是優秀的程式。執行緒安全的優先順序高於效能.
二、Java多執行緒的實現
1、繼承Thread類建立執行緒
Thread類本質上是實現了Runnable介面,啟動該執行緒的唯一方法是start()方法,
public class MyThread extends Thread{
//普通的呼叫方法,定義任務要完成的工作.
@Override
public void run() {
System.out.println("新執行緒正在執行,處理相關的邏輯!");
}
}
public class Test {
public static void main(String[] args) {
//例項化物件
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
//開啟新的執行緒,分配新的資源
myThread1.start();
myThread2.start();
}
}
2、實現Runnable介面建立執行緒
java中是單繼承的,如果繼承了一個類,就不能直接繼承Thread類,需要實現Runnable介面的方式達到開啟新執行緒的目的.
public class MyThread implements Runnable {
//普通的呼叫方法,定義任務要完成的工作.
@Override
public void run() {
System.out.println("新執行緒正在執行,處理相關的邏輯!");
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread =new Thread(myThread);
//開啟新的執行緒,分配新的資源
thread.start();
}
}
3、實現Callable介面
Callable介面的call()方法類似run()方法,都是定義任務要完成的工作.主要不同點是call()方法是有返回值的、可以丟擲異常。Callable型別的任務可以有兩種方法開啟執行.
方法一:藉助FutureTask執行(FutureTask、Callable)
將Callable介面物件放到FutureTask物件中,FutureTask的get()方法,可以獲取返回值.
public class MyCallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("新執行緒正在執行,處理相關的邏輯!");
Thread.sleep(3000);
int sum = 0;
for(int i=0;i<100;i++) {
sum += i;
}
return sum;
}
}
public class Test {
public static void main(String[] args) {
Callable<Integer> mycallabletask = new MyCallableTask();
//由Callable<Integer>建立一個FutureTask<Integer>物件:
FutureTask<Integer> futuretask = new FutureTask<Integer>(mycallabletask);
//註釋:FutureTask<Integer>是一個包裝器,它通過接受Callable<Integer>來建立,它同時實現了Future 和Runnable介面。
//由FutureTask<Integer>建立一個Thread物件:
Thread oneThread = new Thread(futuretask);
oneThread.start();
try {
//通過futuretask中get()方法可以得到MyCallableTask的call()執行結果.
//需要使用時獲取出來,否則出現堵塞,本執行緒要等待新執行緒執行完返回結果才執行
Integer i = futuretask.get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
方法二:藉助執行緒池來執行 (ExecutorService、Callable、Future)
ExecutorService、Callable、Future三個介面實際上都是屬於Executor框架。
執行Callable任務後,可以獲取一個Future的物件,在該物件上呼叫get()就可以獲取到Callable任務返回的Object了。
public class MyCallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("新執行緒正在執行,處理相關的邏輯!");
Thread.sleep(3000);
int sum = 0;
for(int i=0;i<100;i++) {
sum += i;
}
return sum;
}
}
public class Test {
public static void main(String[] args) {
int taskSize = 5;
//建立執行緒池
ExecutorService threadPool = Executors.newCachedThreadPool(taskSize);
//提交一個Callable任務,返回一個Future型別
Future<Integer> future = threadPool.submit(new MyCallableTask());
try {
Thread.sleep(3000);//模擬本執行緒的一些任務
//獲取執行結果get方法是阻塞的
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
採用匿名類直接新建Callable介面
public class Test{
public static void main(String[] args) {
// 建立執行緒池
ExecutorService threadPool = Executors.newCachedThreadPool();
// 提交一個Callable任務,返回一個Future型別
Future<Integer> future = threadPool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("新執行緒正在執行,處理相關的邏輯!");
Thread.sleep(3000);
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
});
try {
Thread.sleep(3000);//模擬本執行緒的一些任務
//獲取執行結果
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、使用場景
一、Tomcat內部採用了多執行緒,上百個使用者同時訪問同一個web應用,都會新開一個執行緒,呼叫到Servlet程式。如果不使用多執行緒,將序列操作,客戶端將等待別人執行完才能訪問。
二、非同步請求,有兩個任務Task a和Task b,單執行緒只能先進行a再進行b。
三、需要知道執行進度,比如說我們常看到的進度條,任務執行到一定進度給new 一個變數,給變數+1.新開一個執行緒去輪詢這個變數,反饋給客戶端,這樣就可以看到進度情況.
總之,很多地方都用到了多執行緒,多執行緒是為了充分利用cpu資源,當你發現一個業務邏輯執行效率特別低,耗時長,可以考慮使用多執行緒.