今日內容
- Lock
- 併發包
- 線程池
教學目標
- 能夠使用Lock解決線程安全問題
- 能夠描述ConcurrentHashMap類的作用
- 能夠描述CountDownLatch類的作用
- 能夠描述CyclicBarrier類的作用
- 能夠表述Semaphore類的作用
- 能夠描述Exchanger類的作用
- 能夠使用線程池
第一章 Lock鎖(掌握)
從jdk5後java.util.concurrent.locks.Lock
機制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大.Lock鎖是Java中更加符合編程習慣的解決方式。
Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了,如下:
public void lock()
:加同步鎖。public void unlock()
:釋放同步鎖。
由於Lock屬於接口,不能創建對象,所以我們可以使用它的子類ReentrantLock來創建對象並使用Lock接口中的函數。
需求:使用Lock實現線程安全的賣票。
分析和步驟:
1)定義一個賣票的任務類SellTicketTask 類並實現Runnable接口;
2)在任務類中定義一個成員變量tickets保存票數100;
3)定義一把鎖Lock的對象l;
4)在run函數中模擬賣票,if語句的上面使用鎖對象l調用lock()函數獲取鎖,等待if語句結束之後,使用鎖對象l調用unlock()函數釋放鎖;
5)定義測試類SellTicketDemo ,在這個類中分別創建任務類的對象和線程類的對象,並使用線程類的對象調用start()函數來啓動線程;
/*
* 需求:使用Lock實現線程安全的賣票。
* Lock是接口,只能通過他的子類ReentrantLock創建對象
* 構造函數 ReentrantLock() 創建一個 ReentrantLock 的實例。
* void lock() 獲取鎖。
* void unlock() 試圖釋放此鎖。
*/
//定義一個任務類用來賣票
class SellTicketTask implements Runnable
{
//定義100張票
private static int tickets=100;
//創建對象作爲任意一把鎖
// private Object obj=new Object();
//定義一把鎖
Lock l=new ReentrantLock();
//模擬賣票
public void run() {
/*while(true)
{
synchronized (obj) {
if(tickets>0)
{
System.out.println(Thread.currentThread().getName()+"出票:"+tickets--);
}
}
}*/
//使用Lock鎖替換synchronized
while(true)
{
//獲取鎖
l.lock();
if(tickets>0)
{
try {Thread.sleep(1);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName()+"出票:"+tickets--);
}
//釋放鎖
l.unlock();
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
// 創建任務類對象
SellTicketTask stt = new SellTicketTask();
//創建線程對象
Thread t1 = new Thread(stt,"窗口1");
Thread t2 = new Thread(stt,"窗口2");
Thread t3 = new Thread(stt,"窗口3");
Thread t4 = new Thread(stt,"窗口4");
//啓動線程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
第二章 併發包
在JDK的併發包裏提供了幾個非常有用的併發容器和併發工具類。供我們在多線程開發中進行使用。
2.1 CopyOnWriteArrayList(理解)
-
ArrayList和CopyOnWriteArrayList效果演示
-
ArrayList在多線程的情況下線程不安全
-
CopyOnWriteArrayList是一個線程安全的集合,解決ArrayList安全問題。 數據不唯一。
- 這個類的內部其實也就是使用了同步鎖解決線程安全問題,和我們自己寫同步鎖是一樣的意思。
-
-
需求:給list集合添加1000個元素,然後打印集合的長度。
public class MyRun implements Runnable{ //定義一個集合 //普通的ArrayList是線程不安全的類 //static ArrayList<Integer> list = new ArrayList<>(); //只要換成CopyOnWriteArrayList就變成了線程安全的 static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(); @Override public void run() { //給集合添加了1000個元素 for (int i = 0; i < 1000; i++) { list.add(i); } } } public class Demo_ArrayList { public static void main(String[] args) throws InterruptedException { MyRun mr = new MyRun(); //開啓線程 Thread t1 = new Thread(mr); t1.start(); Thread t2 = new Thread(mr); t2.start(); //爲了讓for循環先執行結束再打印 //睡兩秒鐘 Thread.sleep(2000); //在這裏打印集合的長度,裏面應該存放了多少個元素? System.out.println(MyRun.list.size()); } } 執行效果: ArrayList: 一個線程給集合添加1000個元素,兩個線程應該添加2000個元素 但是打印的結果是小於2000或者可能會拋異常 因爲ArrayList集合本身就是線程不安全的 CopyOnWriteArrayList: 結果是2000
2.2 CopyOnWriteArraySet(理解)
- HashSet是線程不安全的
- CopyOnWriteArraySet是線程安全的
- 需求:向HashSet中存儲1000個數字。
代碼演示如下:
package com.itheima.sh.demo_14;
public class MyRun implements Runnable {
//定義集合
//HashSet集合本身是有線程安全問題的
// static HashSet<Integer> set = new HashSet<>();
//CopyOnWriteArraySet解決了安全問題
static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
@Override
public void run() {
//添加1000個數字 0-999
for (int i = 0; i < 1000; i++) {
set.add(i);
}
}
}
public class Test01 {
public static void main(String[] args) throws InterruptedException {
MyRun mr = new MyRun();
//開啓線程
Thread t1 = new Thread(mr);
t1.start();
//開啓線程
Thread t2 = new Thread(mr);
t2.start();
//讓主程序睡兩秒鐘
Thread.sleep(2000);
//打印集合的長度 大於1000
System.out.println(MyRun.set.size());
}
}
執行效果:
HashSet:
本來集合中保存的是1000個元素,但是打印的長度size大於1000.
CopyOnWriteArraySet:
打印的集合長度就是1000
說明:上述打印集合的長度size大於1000的原因如下圖所示:
假設線程一和線程二都向set集合中添加數據0,線程一正在添加的過程中先判斷集合中是否含有0,如果沒有則添加數字0,此時線程二也開始判斷set集合中是否有0,此時沒有則線程二也準備添加,添加一次,那麼底層size就會+1,但實際上set集合只存儲一個數字0.size加錯了。
注意:
CopyOnWriteArraySet 底層就是CopyOnWriteArrayList 。CopyOnWriteArraySet 如何保證唯一的?使用CopyOnWriteArrayList中的方法:
boolean addIfAbsent(E e) 添加元素(如果不存在)。
如果添加的元素存在,則不存儲,如果添加元素不存在則添加。
舉例:
aaa 第一次添加可以添加
bbb 第一次添加可以添加
aaa 已經存在aaa 不添加
2.3 ConcurrentHashMap(理解)
-
HashMap和Hashtable和ConcurrentHashMap效果演示
-
需求:向HashMap集合中存儲1000個鍵值對數據
import java.util.HashMap; import java.util.Hashtable; import java.util.concurrent.ConcurrentHashMap; public class MyRun implements Runnable { //HashMap線程不安全 //HashMap<Integer,Integer> map = new HashMap<>(); //Hashtable線程安全的 //Hashtable<Integer,Integer> map = new Hashtable<>(); //ConcurrentHashMap線程安全的 ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>(); @Override public void run() { //添加1000個元素 0-999 for (int i = 0; i < 1000; i++) { map.put(i,i); } } } public class DemoHashMap { public static void main(String[] args) throws InterruptedException { MyRun mr = new MyRun(); //開啓線程 Thread t1 = new Thread(mr); t1.start(); //開啓線程 Thread t2 = new Thread(mr); t2.start(); //先睡2秒鐘讓循環執行結束 Thread.sleep(2000); //打印集合的長度 System.out.println(mr.map.size()); } } 執行效果: HashMap本來應該打印的長度是1000,但是執行的結果大於1000 Hashtable 結果是1000 ConcurrentHashMap 結果是1000
Hashtable和ConcurrentHashMap的速度區別
-
Hashtable解決線程安全的方式,但是效率很低。
public synchronized V get(Object key) {} public synchronized V put(K key, V value) {} 每個方法全都是同步的,當任意一個方法在執行時,別的方法都不能執行。
HashTable容器使用synchronized來保證線程安全,但在線程競爭激烈的情況下HashTable的效率非常低下。因爲當一個線程訪問HashTable的同步方法,其他線程也訪問HashTable的同步方法時,會進入阻塞狀態。如線程1使用put進行元素添加,線程2不但不能使用put方法添加元素,也不能使用get方法來獲取元素,所以競爭越激烈效率越低。
-
ConcurrentHashMap
CAS + 局部(synchronized)鎖定
-
-
Hashtable和ConcurrentHashMap效率高低的代碼演示
public class MyRun implements Runnable { //HashMap線程不安全 //HashMap<Integer,Integer> map = new HashMap<>(); //Hashtable線程安全的 //Hashtable<Integer,Integer> map = new Hashtable<>(); //ConcurrentHashMap線程安全的 ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>(); @Override public void run() { //獲取系統當前時間 long time1 = System.currentTimeMillis(); //添加1000個元素 0-1999 for (int i = 0; i < 2000; i++) { map.put(i,i); } long time2 = System.currentTimeMillis(); System.out.println((time2-time1) + "毫秒"); } } public class DemoHashMap { public static void main(String[] args) throws InterruptedException { MyRun mr = new MyRun(); //開啓線程 //開啓了很多個線程能夠看到更明顯的速度的區別 for (int i = 0; i < 1000; i++) { Thread t1 = new Thread(mr); t1.start(); } } }
2.4 CountDownLatch(瞭解)
-
作用
CountDownLatch允許一個或多個線程等待其他線程完成操作。
-
方法介紹
public CountDownLatch(int count)// 初始化一個指定計數器的CountDownLatch對象 void await() // 讓當前線程等待 void countDown() //每調用一次countDown()方法,計數器count就會進行減1,如果減到0等待的線程就會執行
-
代碼演示
- 要求: 必須在先打印C 再打印B
import java.util.concurrent.CountDownLatch; public class MyRun1 implements Runnable { CountDownLatch latch; //使用構造方法傳入參數 public MyRun1(CountDownLatch latch){ this.latch = latch; } @Override public void run() { System.out.println("A"); /*try { Thread.sleep(1000);//效率太低 } catch (InterruptedException e) { e.printStackTrace(); }*/ //等待 try { latch.await(); } catch (InterruptedException e) { } System.out.println("B"); } } import java.util.concurrent.CountDownLatch; public class MyRun2 implements Runnable{ CountDownLatch latch; //使用構造方法傳入參數 public MyRun2(CountDownLatch latch){ this.latch = latch; } @Override public void run() { System.out.println("C"); //計數器減一 //latch在計數器等於0的時候就會讓等待的線程執行 latch.countDown(); } } import java.util.concurrent.CountDownLatch; //有一個要求:必須在先打印C 再打印B public class Test01 { public static void main(String[] args) throws InterruptedException { //創建對象 //這裏的參數1表示計數器,只要執行latch.countDown();那麼計數器就會減1 CountDownLatch latch = new CountDownLatch(1); //開啓線程 MyRun1 myRun1 = new MyRun1(latch); Thread t = new Thread(myRun1); t.start(); //開啓線程 MyRun2 myRun2 = new MyRun2(latch); Thread t2 = new Thread(myRun2); t2.start(); } }
如果沒有使用CountDownLatch的情況下,會出現如下所示結果:
小結:
CountDownLatch中count down是倒數的意思,latch則是門閂的含義。整體含義可以理解爲倒數的門栓,似乎有一點“三二一,芝麻開門”的感覺。
CountDownLatch是通過一個計數器來實現的,每當一個線程完成了自己的任務後,可以調用countDown()方法讓計數器-1,當計數器到達0時,調用CountDownLatch的await()方法的線程阻塞狀態解除,繼續執行。
2.5 CyclicBarrier(瞭解)
概述
CyclicBarrier的字面意思是可循環使用(Cyclic)的屏障(Barrier)。
-
作用
讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個線程到達屏障時,屏障纔會開門,所有被屏障攔截的線程纔會繼續運行。
-
方法介紹
public CyclicBarrier(int parties, Runnable barrierAction) // 創建對象 //parties代表要達到屏障的線程數量 //barrierAction達到屏障之後要執行的線程 int await()// 每個線程調用await方法告訴CyclicBarrier我已經到達了屏障,然後當前線程被阻塞
-
示例代碼
需求:有5個學生一起考試,考試有100道題,每個人答題速度不一樣,但是要求,所有人做完之後才能交卷。
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class MyRun implements Runnable { //創建屏障對象 CyclicBarrier cb = new CyclicBarrier(5,new JiaoJuan()); @Override public void run() { //考試一共100道題 for (int i = 1; i <= 100; i++) { System.out.println(Thread.currentThread().getName() + "做完了第" + i + "道題"); } System.out.println(Thread.currentThread().getName() + "做完了所有題!"); //等待別人寫題 try { cb.await(); } catch (InterruptedException e) { } catch (BrokenBarrierException e) { } } } public class JiaoJuan implements Runnable { @Override public void run() { System.out.println("大家一起交卷!!!!"); } } public class Demo01 { public static void main(String[] args) { //創建線程 MyRun mr = new MyRun(); //開啓5個線程 new Thread(mr,"張三").start(); new Thread(mr,"李四").start(); new Thread(mr,"王五").start(); new Thread(mr,"趙六").start(); new Thread(mr,"田七").start(); } }
使用場景
使用場景:CyclicBarrier可以用於多線程計算數據,最後合併計算結果的場景。
例如:使用兩個線程讀取2個文件中的數據,當兩個文件中的數據都讀取完畢以後,進行數據的彙總操作。
2.6 Semaphore(瞭解)
-
作用
1)Semaphore的主要作用是控制線程的併發數量。
2)之前學習的synchronized可以起到"鎖"的作用,但某個時間段內,只能有一個線程允許執行。Semaphore 可以設置同時允許幾個線程執行。
3)Semaphore字面意思是信號量的意思,它的作用是控制訪問特定資源的線程數目。
-
方法介紹
public Semaphore(int permits) //permits 表示許可線程的數量 void acquire() //表示獲取許可 void release() //表示釋放許可
-
代碼演示
需求:開了一個飯店,飯店裏面只有3張桌子,有5桌人來喫飯。
//飯店 public class FanDian { //定義Semaphore控制最大接客數量 //public Semaphore(int permits)permits 表示許可線程的數量 Semaphore s = new Semaphore(3); //飯店的服務喫飯方法(方法名無所謂) public void service() throws InterruptedException { //獲取方法 //public void acquire() throws InterruptedException 表示獲取許可 s.acquire(); //調用了這個方法就表示進來了 //顯示進來的時間 Calendar c = Calendar.getInstance(); int minute = c.get(Calendar.MINUTE); int second = c.get(Calendar.SECOND); System.out.println(Thread.currentThread().getName() + minute + "分鐘" + second + "秒進來喫飯"); //喫飯 //等待3秒鐘 模擬喫飯的過程,表示線程正在喫飯 Thread.sleep(3000); //顯示出去的時間 Calendar c2 = Calendar.getInstance(); int minute2 = c2.get(Calendar.MINUTE); int second2 = c2.get(Calendar.SECOND); System.out.println(Thread.currentThread().getName() + minute2 + "分鐘" + second2 + "秒喫完飯出去了"); //釋放 //public void release() 表示釋放許可 s.release(); } } public class MyRun implements Runnable { //創建對象 FanDian fd = new FanDian(); @Override public void run() { //調用喫飯的方法 try { fd.service(); } catch (InterruptedException e) { } } } public class Test01 { public static void main(String[] args) throws InterruptedException { //創建線程 MyRun mr = new MyRun(); //開啓5個線程 new Thread(mr,"張三").start(); new Thread(mr,"李四").start(); new Thread(mr,"王五").start(); new Thread(mr,"鎖哥").start(); new Thread(mr,"趙六").start(); } }
2.7 Exchanger(瞭解)
概述
-
作用
Exchanger(交換者)是一個用於線程間協作的工具類。Exchanger用於進行2個線程間的數據交換。
說明:
兩個線程通過exchange()方法交換數據,如果第一個線程先執行exchange()方法,它會一直等待第二個線程也執行exchange()方法,當兩個線程都到達同步點時,這兩個線程就可以交換數據,將本線程生產出來的數據傳遞給對方。
-
方法介紹
public Exchanger() //構造方法,創建對象 public V exchange(V x) //參數是給對方的數據,返回值是對方發來的數據 //說明:如果一個線程執行了exchange(),此時就會阻塞等待另一個線程執行exchange()方法,才結束阻塞
-
代碼演示
需求:在一個銀行,要統計今年收入一共是多少,讓兩個人都去計算,算完之後兩個人互相交換數據。
import java.util.concurrent.Exchanger; public class MyRun1 implements Runnable { Exchanger<String> e; //構造方法 public MyRun1(Exchanger<String> e) { this.e = e; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "開始計算今年的收入。。"); String s = null; try { s = e.exchange("收入表A"); } catch (InterruptedException e1) { } System.out.println(Thread.currentThread().getName() + "收到了 " + s); } } public class MyRun2 implements Runnable { Exchanger<String> e; //構造方法 public MyRun2(Exchanger<String> e) { this.e = e; } @Override public void run() { System.out.println(Thread.currentThread().getName() + "開始計算今年的收入。。。"); String s = null; try { s = e.exchange("收入表B"); } catch (InterruptedException e1) { } System.out.println(Thread.currentThread().getName() + "收到了 " +s); } } import java.util.concurrent.Exchanger; public class Demo { public static void main(String[] args) { //在主方法中創建對象 //泛型用來代表交換的數據的類型 Exchanger<String> e = new Exchanger<>(); MyRun1 myRun1 = new MyRun1(e); new Thread(myRun1,"張三").start(); MyRun2 myRun2 = new MyRun2(e); new Thread(myRun2,"李四").start(); } }
使用場景
使用場景:可以做數據校對工作
需求:比如我們需要將紙製銀行流水通過人工的方式錄入成電子銀行流水。爲了避免錯誤,採用AB崗兩人進行錄入,錄入到兩個文件中,系統需要加載這兩個文件,
並對兩個文件數據進行校對,看看是否錄入一致,
【自學,瞭解,有興趣的同學可以自學下】
- exchange方法的超時
1).製作線程A:
public class ThreadA extends Thread {
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
try {
System.out.println("線程A欲傳遞值'禮物A'給線程B,並等待線程B的值,只等5秒...");
System.out.println("在線程A中得到線程B的值 =" + exchanger.exchange("禮物A",5, TimeUnit.SECONDS));
System.out.println("線程A結束!");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
System.out.println("5秒鐘沒等到線程B的值,線程A結束!");
}
}
}
2).製作測試類:
public class Run {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<String>();
ThreadA a = new ThreadA(exchanger);
a.start();
}
}
3).測試結果:
第三章 線程池方式
3.1 線程池的思想
我們使用線程的時候就去創建一個線程,這樣實現起來非常簡便,但是就會有一個問題:
如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因爲頻繁創建線程和銷燬線程需要時間。
那麼有沒有一種辦法使得線程可以複用,就是執行完一個任務,並不被銷燬,而是可以繼續執行其他的任務?
在Java中可以通過線程池來達到這樣的效果。今天我們就來講解一下Java的線程池。
3.2 線程池概念
程序啓動一個新線程成本是比較高的,因爲它涉及到要與操作系統進行交互。因爲啓動線程的時候會在內存中開闢一塊空間,消耗系統資源,同時銷燬線程的時候首先要把和線程相關東西進行銷燬,還要把系統的資源還給系統。這些操作都會降低操作性能。尤其針對一個線程用完就銷燬的更加降低效率。
而使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,生存期較短的線程指的是用完一次線程就丟掉。更應該考慮使用線程池。
線程池裏的每一個線程代碼結束後,並不會死亡,而是再次回到線程池中成爲空閒狀態,等待下一個對象來使用。
線程池工作原理如下圖所示:
需求:我有一段任務,需要執行100次。
說明:
每次線程執行完任務以後,線程不會銷燬,會放回線程池中,每次在執行任務的時候又會到線程池中去取線程。這樣會提高效率。
合理利用線程池能夠帶來三個好處:
- 降低資源消耗。減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
- 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
- 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線程的數目,防止因爲消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機或者宕機)。
3.3 線程池的使用
Java裏面線程池的頂級接口是java.util.concurrent.Executor
,以及他的子接口java.util.concurrent.ExecutorService
。
要配置一個線程池是比較複雜的,尤其是對於線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,因此在java.util.concurrent.Executors
線程工廠類裏面提供了一些靜態工廠,生成一些常用的線程池。官方建議使用Executors工程類來創建線程池對象。
Executors類中有個創建線程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回線程池對象。(創建的是有界線程池,也就是池中的線程個數可以指定最大數量)
獲取到了一個線程池ExecutorService 對象,那麼怎麼使用呢,在這裏定義了一個使用線程池對象的方法如下:
-
public Future<?> submit(Runnable task)
:獲取線程池中的某一個線程對象,並執行Future接口:用來記錄線程任務執行完畢後產生的結果。
使用線程池中線程對象的步驟:
A:自定義一個類,作爲任務類並實現Runnable接口;
B:實現Runnable接口中的run方法;
C:創建任務類的對象;
D:獲取線程池對象;
E:直接執行任務;
需求:使用線程池來完成賣票任務。
Runnable實現類代碼:
//A:自定義一個類,作爲任務類並實現Runnable接口;
class SellTicketTask implements Runnable
{
//定義成員變量存儲100張票
private static int tickets=100;
//創建鎖對象
private Lock l=new ReentrantLock();
//B:實現Runnable接口中的run方法;
public void run() {
// 模擬賣票
while(true)
{
//獲取鎖
l.lock();
if(tickets>0)
{
//休眠
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"出票:"+tickets);
tickets--;
}
//釋放鎖
l.unlock();
}
}
}
線程池測試類:
public class SellTicketDemo {
public static void main(String[] args) {
//C:創建任務類的對象;
SellTicketTask stt = new SellTicketTask();
//D:獲取線程池對象; 獲取2個線程
ExecutorService es = Executors.newFixedThreadPool(2);
//E:直接執行任務;
//自己創建線程對象的方式
// Thread t = new Thread(stt);
// t.start(); ---> 調用MyRunnable中的run()
// 從線程池中獲取線程對象,然後調用SellTicketTask中的run()
es.submit(stt);
// 再獲取個線程對象,調用SellTicketTask中的run()
es.submit(stt);
}
}
3.4 Callable開啓多線程
-
<T> Future<T> submit(Callable<T> task)
: 獲取線程池中的某一個線程對象,並執行.問題1:Callable是什麼?
/*
* 演示:演示Callable
* 我們忽略返回值,這個接口就與Runnable接口一樣了
*/
class MyTask implements Callable<Object>{
@Override
public Object call() throws Exception {
for( int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " ... " + i);
}
return null;
}
}
public class CallableDemo {
public static void main(String[] args) {
// 創建任務對象
MyTask mt = new MyTask();
// 獲取線程池
ExecutorService es = Executors.newFixedThreadPool(2);
// 執行任務
es.submit(mt);
es.submit(mt);
}
}
問題2:Future是什麼?
- 方法:
V get()
: 獲取計算完成的結果。 - 需求:通過Callable計算從1到5的和
A:我們自定義類,實現Callable接口
B:實現Call方法,Call方法有返回值
C:然後吧任務類對象交給線程池執行
D:執行完成的結果保存Future中
E:最後我們調用Future的get方法拿到真正的結果。
/*
* 演示:帶返回值的線程任務
* 需求:通過Callable計算從1到任意數字的和
*/
class SumTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1; i <= 5; i++){
sum += i;
}
return sum ;
}
}
public class CallableDemo02 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 創建任務對象
SumTask st = new SumTask();
// 獲取線程池
ExecutorService es = Executors.newFixedThreadPool(1);
// 執行任務
Future<Integer> future = es.submit(st);
// 等待運算結束,獲取結果
Integer i = future.get();
System.out.println(i);
}
}