java如何写线程外部类_廖雪峰Java读书笔记(六)--多线程(或称并发)
1. 多線程基礎
首先要明白一些概念:
進程:把一個任務稱為一個進程,瀏覽器就是一個進程,視頻播放器是另一個進程,類似的,音樂播放器和Word都是進程。
線程:某些進程內部還需要同時執行多個子任務。例如,我們在使用Word時,Word可以讓我們一邊打字,一邊進行拼寫檢查,同時還可以在后臺進行打印,我們把子任務稱為線程。注:操作系統調度的最小單位是線程。
進程和線程的關系就是:一個進程可以包含一個或多個線程,但至少會有一個線程。每個進程都有自己完整的變量,但一個進程內的線程共享數據。(The essential difference is that while each process has a complete set of its own variables, threads share the same data.)
Java用一個主線程來執行main()方法,在main()方法內部我們又可以啟動多個線程。
2. 創建新線程
要創建一個新線程非常容易,我們需要實例化一個Thread實例,然后調用它的start()方法即可。
package MultiThr;
public class CreateThread {
public static void main(String[] args) {
Thread t = new Thread();
t.start(); // 啟動新線程
}
}
但這個線程什么都沒有做。如果要創建一個做事的線程,應該這樣做。
方法一:
來自于《Java核心技術》,其實是廖雪峰的方法二,但這個用到了lambda表達式。
實現Runnable接口,并實現其中的run方法。Runnable接口的源代碼如下 :
public interface Runnable{
void run();
}
這是個函數式接口,所以可以用lambda表達式來實現之:
Runnable r = () -> { /*在此處寫下要執行的代碼*/ };
構建一個Thread對象,并傳入剛才實現的Runnable接口。
Thread t = new Thread(r);
調用start()方法:
t.start();
注:在Runnable的實現中要catch (InterruptedException e)。
// 廖雪峰給出的例子
public class Main{
public static void main(String[] args){
Thread t = new Thread(new MyRunnable());
t.start();
}
class MyRunnable implements Runnable{
@Override
public void run(){
System.out.println("start new thread");
}
}
}
如果用《Java核心技術》的方法改寫上述代碼,是:
public class Main{
public static void main(String[] args){
Thread t = new Thread(new Runnable() ->{ // 這里用到了lambda表達式方法
try{
System.out.println("start new thread");
} catch (InterruptedException e){ }
}
);
t.start();
}
}
方法二:
廖雪峰的方法一,《Java核心技術》中的方法二。
操作:從Thread繼承一個類出來,并@Override其中的run()方法。
示例:
// 將上面的例子用此方法改寫如下:
public class Main{
public static void main(String[] args){
Thread t = new MyThread();
t.start();
}
}
class MyThread extends Thread{
@Override
public void run(){
/*task codes*/
}
}
小結:
Thread(Runnable target):實例化新對象Thread并調用其中的run()方法。具體如何調用,看上面的博客文章;
void start():開始線程;
void run():一定要復寫之。可以開一個新類extends Thread,也可以實現public inteface Runnable接口;
static void sleep(long millis):線程睡眠一定時間
3. 線程狀態
線程有六態,分別是:
New:新創建的線程,尚未執行。
Runnable:運行中的線程,正在執行run()方法的Java代碼。Runnable的狀態是可執行可不執行,只要開始執行沒結束,不管是在執行中還是在休息,都是Runnable。
Blocked:運行中的線程,因為某些操作被阻塞而掛起;
Waiting:運行中的線程,因為某些操作在等待中。和上面的Blocked狀態區別不大。
Timed Waiting:運行中的線程,因為執行sleep()方法正在計時等待;
Terminated:線程已終止,因為run()方法執行完畢。
當線程啟動后,它可以在Runnable、Blocked、Waiting和Timed Waiting這幾個狀態之間切換,直到最后變成Terminated狀態,線程終止。
注:New和Terminated是兩頭,其他的是中間。
線程終止的原因有:
線程正常終止:run()方法執行到return語句返回;
線程意外終止:run()方法因為未捕獲的異常導致線程終止;
對某個線程的Thread實例調用stop()方法強制終止(強烈不推薦使用)。
注:
沒捕獲到的異常也可以終止線程
別用stop()方法!
使用join()方法會使線程暫停,等其他線程結束了再來。也就是“讓道”。
4. 線程屬性
廖雪峰部分講到了兩個:
中斷線程(interrupted status)
守護線程(daemon threads)
在《Java核心技術》中還有一個,暫時不知如何翻譯,因為我讀的是英文版:
handlers for uncaught exceptions
4.1 中斷線程
有兩種情況會讓線程中斷:
進入return狀態
沒能捕捉異常
以下方法可以檢查線程是否設置了中斷狀態,首先我們可以調用Thread.currentThread()方法來獲取當前線程,然后調用isInterrupted()檢查是否設置了interrupted()狀態。但如果線程被鎖定便不能檢查中斷狀態了。這就是InterruptedException的來源。
在catch到InterruptedException之后,可以檢查一下Thread.currentThread().interrupt();也可以在方法之前就預先拋出Exception,像這樣:
public void run() throws InterruptedException
4.2 守護線程
Java程序入口就是由JVM啟動main線程,main線程又可以啟動其他線程。當所有線程都運行結束時,JVM退出,進程結束。如果有一個線程沒有退出,JVM進程就不會退出。所以,必須保證所有線程都能及時結束。
注:也就是說,一切方法都要在main()方法中執行。
守護線程是指為其他線程服務的線程。在JVM中,所有非守護線程都執行完畢后,無論有沒有守護線程,虛擬機都會自動退出。
注:守護線程除了服務其他線程以外沒有其他的作用。(《Java核心技術》)原文:A daemon is simply a thread that has no other role in life than to serve others.
設置守護線程的方法:
t.setDaemon(true)
4.3 為線程取名
可以給線程命名:
Thread t = new Thread(runnable);
t.setName("abc"); # 使用setName("name")方法
4.4 解決未捕獲的異常問題
線程也可以被未捕獲的異常終止,然后線程死亡。這時候就需要處理未捕獲的異常。可以實現Thread.UncaughtExceptionHandler接口來處理。如下:
void unchangedException(Thread t, Throwable e)
可以以線程中設置一個setUncaughtExceptionHandler方法,也可以設置靜態方法setDefaultUncaughtExceptionHandler。
ThreadGroup對象暫時不是很懂,在《Core Java》P747-748中。就暫時先翻譯一下:
ThreadGroup類實現了Thread.UncaughtExceptionHandler接口。其中的uncaughtException方法進行以下操作:
如果線程組有父線程,那么會調用uncaughtException()方法;
否則,如果Thread.getDefaultUncaughtExceptionHandler方法返回一個非null的解決方案,便會調用uncaughtException方法;
如果Throwable是ThreadDeath的實例,那么什么也不會發生。
線程的名字及Throwable堆棧追蹤會被輸出 。
5. 線程同步(Synchronization)
前面說過,同一進程內的多個線程共享數據,這樣會導致競爭情況(race condition)。也就是,當多個線程同時運行時, 線程并不是非常有禮貌地排隊運行,如果不加干預,就是你運行一會它運行一會。如果是兩個線程同時運行,并不是兩個各運行相等的時間。要注意。
廖老師的例子:
// https://www.liaoxuefeng.com/wiki/1252599548343744/1306580844806178
public class Main {
public static void main(String[] args) throws Exception {
var add = new AddThread(); // 線程一
var dec = new DecThread(); // 線程二
// 兩個線程并不是“先來后到”式運行的
add.start();
dec.start();
add.join();
dec.join();
System.out.println(Counter.count); // 最后的結果不一定是0
}
}
class Counter {
public static int count = 0;
}
class AddThread extends Thread {
public void run() {
for (int i=0; i<10000; i++) { Counter.count += 1; }
}
}
class DecThread extends Thread {
public void run() {
for (int i=0; i<10000; i++) { Counter.count -= 1; }
}
}
很可能的運行模式如下圖:
從廖老師網站上截圖下來
那么,如果要讓線程之間可以禮讓地運行,遵循“先來后到”的順序,怎么辦?就像這樣:
從廖老師網站截圖下來
從圖中可以看出,為保證代碼可以“先來后到”地運行,需要通過lock(加鎖)與unlock(解鎖)操作實現。通過加鎖與解鎖的操作就可以保證一個線程執行期間不會有其他的線程進入此指令區間。即使在執行期線程被操作系統中斷執行,其他線程也會因為無法獲得鎖導致無法進入此指令區間。只有執行線程將鎖釋放后,其他線程才有機會獲得鎖并執行。在專業術語中,此操作叫做代碼的原子性(atomic)。
有鎖與無鎖的區別(圖片來自《Java核心技術》)
有兩種方法可以實現:ReetrantLock類型與synchronized關鍵字。《Java核心技術》先講的是前者,我看書有點懵逼,但廖老師先講的是后者,相對比較明白一些。
由上面可以得知,保證一段代碼的原子性,可以通過加鎖與解鎖的操作來實現。不過,在《Java核心技術》中作者也承認:
The Lock and Condition interfaces give programmers a high degree of control over locking. However, in most situations, you don't need that control -- you can use a mechanism that is built into the Java language.
其實,“a mechanism that is built into the Java language”就是我們要說的synchronized關鍵字,在術語中稱為intrinsicLock。
兩種方法使用:
synchronized(lock)
public synchronized void method()
第一種使用方式表示用lock實例作為鎖,兩個線程在執行各自的synchronized(Counter.lock) { ... }代碼塊時,必須先獲得鎖,才能進入代碼塊進行。執行結束后,在synchronized語句塊結束會自動釋放鎖。第二種方法也不用寫unlock。 但是,它的缺點是帶來了性能下降。因為synchronized代碼塊無法并發執行。此外,加鎖和解鎖需要消耗一定的時間,所以,synchronized會降低程序的執行效率。(廖雪峰語)
如何使用synchronized關鍵字鎖定對象呢?
找出修改共享變量的線程代碼塊;
選擇一個共享實例作為鎖;
使用synchronized(lockObject) { ... }。
注意:
如果要兩個線程對同一個對象先來后到地操作,那么兩個線程應當鎖住同一個對象;使用synchronized的時候,獲取到的是哪個鎖非常重要。鎖對象如果不對,代碼邏輯就不對。
對幾個變量要進行鎖操作,就設幾個鎖。
JVM規范定義了幾種原子操作。不需要同步:
基本類型(long和double除外)賦值,例如:int n = m;
引用類型賦值,例如:List list = anotherList
如果一個類被設計為允許多線程正確訪問,我們就說這個類就是“線程安全”的(thread-safe)。Java標準庫的java.lang.StringBuffer也是線程安全的。
如果是第二種方法,可以把整個方法變為同步代碼塊,鎖住的對象是this。
如果鎖住的是static方法,那么鎖住的是class.Class對象本身。
5.1 死鎖
首先我們要明白,Java線程鎖是可重入的鎖(廖雪峰語)。對同一個線程,鎖可以重復獲得,即JVM允許線程重復地獲取同一個鎖,這就是可重入鎖。例如:
public class Counter {
private int count = 0;
public synchronized void add(int n) { // add方法會獲取一個鎖
if (n < 0) {
dec(-n); // 同時dec方法也會獲得鎖
// JVM同時允許add()與dec()獲得鎖
} else {
count += n;
}
}
public synchronized void dec(int n) {
count += n;
}
}
那么死鎖是怎么發生的呢?通俗地說,一個已經獲取鎖的對象還要再獲取另一個鎖,但另一個鎖已經被其他對象把持,死鎖就發生了。(我自己說的)例如:(廖雪峰老師的例子)
public void add(int m) {
synchronized(lockA) { // 獲得lockA的鎖
this.value += m;
synchronized(lockB) { // 獲得lockB的鎖
this.another += m;
} // 釋放lockB的鎖
} // 釋放lockA的鎖
}
public void dec(int m) {
synchronized(lockB) { // 獲得lockB的鎖
this.another -= m;
synchronized(lockA) { // 獲得lockA的鎖
this.value -= m;
} // 釋放lockA的鎖
} // 釋放lockB的鎖
}
(用廖老師的方法)
分析如上例子:線程1和線程2分別執行add()與dec()時:
線程1:進入add()方法,獲得lockA;
線程2:進入dec()方法,獲得lockB;
然后順序執行:
線程1:獲取lockB失敗,因為已經被dec()獲取
線程2:獲取lockA失敗,因為已經被add()獲取
于是死鎖(Deadlocks)就發生了。死鎖一旦形成就只能強制結束進程。避免死鎖的方法是嚴格按照線程獲取鎖的順序來寫!
一旦死鎖發生,那么可以按Ctrl + \來查看所有線程。每個線程都有追蹤,告訴你在哪里鎖住了。
5.2 使用wait與notify
Java中synchronized解決了多線程競爭的問題,但并不解決多線程協調的問題。先看一個例子:
package MultiThr;
import java.util.*;
public class TaskQueue {
Queue queue = new LinkedList<>();
public synchronized void addTask(String s){
this.queue.add(s);
}
public synchronized String getTask(){
while (queue.isEmpty()){} // 實際上while循環不會停下來
return queue.remove();
}
}
理論上,如果任務隊列為空,就等待,直到線程里有一個任務就退出。但事實上不是這樣的:實際運行中,因為線程在執行while()循環時,已經在getTask()入口處獲得了this鎖,導致其他線程無法調用getTask()方法。最后的結果是getTask()陷入死循環。如何修改呢?
package MultiThr;
import java.util.*;
public class TaskQueue {
Queue queue = new LinkedList<>();
public synchronized void addTask(String s){
this.queue.add(s);
}
public synchronized String getTask() throws InterruptedException { // 拋出異常必須
while (queue.isEmpty()){
// 釋放this鎖
this.wait();
}
return queue.remove();
}
}
當一個線程在this.wait()等待時,它就會釋放this鎖,從而使得其他線程能夠在addTask()方法上獲得this鎖。當wait()方法調用時會釋放線程鎖,返回后又重新獲得鎖。
當我們用wait()方法讓線程進入等待狀態后,有什么方法可以重新喚起線程嗎?notify()方法,可以喚醒一個正在this鎖等待的線程。方法如下:
public synchronized void addTask(String s) {
this.queue.add(s);
this.notify(); // 喚醒在this鎖等待的線程
}
5.3 再談ReentrantLock
由于《Java核心技術》一上來就講到了ReentrantLock,我有點不懂。現在學完之后再回來看ReentrantLock,似乎可以懂了。
首先,ReentrantLock在java.uitl.concurrent.locks,屬于并發編程。
上面的Counter類例子:
public class Counter {
private int count;
public void add(int n) {
synchronized(this) {
count += n;
}
}
}
用ReentrantLock改造一下是(廖老師例子):
import java.util.concurrent.locks.*;
public class Counter{
private int count;
private final Lock lock = new ReentrantLock(); // 在方法外圍定義一個鎖
public void add(int n){
lock.lock(); // 方法最一開始就加鎖
try{ // 要用try語句
count += n;
} finally{ lock.unlock(); } // 一定要釋放鎖
}
}
注1:(《Java核心技術》語) It is critically important that the unlock operation is enclosed in a finally clause. (譯:在finally語句中使用unlock語句釋放鎖)
注2:(《Java核心技術》語) You cannot use the try-with-resources statement. (譯:在ReentrantLock中,不能使用try帶括號)
ReentrantLock也是可重入鎖,也就是說一個線程可以多次獲取同一個鎖,得鎖者運行。
前面已經說到用wait()方法和notifyAll()方法實現多線程協調。那么在ReentrantLock中有何方法?Condition類中提供的await()、signal()、signalAll()可以實現同樣的功能。
手擼一遍廖老師的代碼:
import java.util.concurrent.locks.*;
public class TaskQueue{
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private Queue queue = new LinkedList<>();
public void addTask(String S){
lock.lock();
try{
queue.add(s);
condition.signalAll(); // notifyAll()
}finally{
lock.unlock();
}
}
public void getTask(){
lock.lock();
try{
while (queue.isEmpty()){
condition.await();
}finally{ lock.unlock(); }
return queue.remove();
}
}
}
5.4 讀寫鎖
不論是synchronized()方法也好,還是ReentrantLock也罷,都只能允許一個線程進行讀寫,如果不寫入的話,其他線程讀取都困難,因為沒有獲取鎖。但我們想要的效果是允許多個線程同時讀,但只要有一個線程在寫,其他線程就必須等待。換句話說,如果沒人寫,其他的都可以讀;如果寫了,其他的就不可讀了。這個問題可以用ReadWriteLock解決。
使用ReadWriteLock時,適用條件是同一個數據,有大量線程讀取,但僅有少數線程修改。比如論壇的帖子。
但它有一個問題:既某個線程要寫的時候,需要釋放讀鎖才能寫。于是悲觀鎖發生了。
示例:
package MultiThr;
import java.util.concurrent.locks.*;
import java.util.*;
public class CounterRW {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock rLock = rwLock.readLock();
private final Lock wLock = rwLock.writeLock();
private int[] counts = new int[10];
// 把讀寫操作分別用讀鎖和寫鎖來加鎖,在讀取時,多個線程可以同時獲得讀鎖,這樣就大大提高了并發讀的執行效率。
public void inc(int index){
wLock.lock(); // 寫鎖
try{
counts[index] += 1;
}finally{
wLock.unlock();
}
}
public int[] get(){
rLock.lock(); // 讀鎖
try{
return Arrays.copyOf(counts, counts.length);
}finally{ rLock.unlock(); }
}
}
5.5 悲觀鎖與樂觀鎖
先來看廖雪峰老師的簡版定義:
悲觀鎖:如果有線程正在讀,寫線程需要等待讀線程釋放鎖后才能獲取寫鎖,即讀的過程中不允許寫,這是一種悲觀的讀鎖。
樂觀鎖:讀的過程中大概率不會有寫入,因此被稱為樂觀鎖。
再來看看技術博客中如何定義:
悲觀鎖:總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。synchronized與ReentrantLock還有ReadWriteLock都屬于悲觀鎖范圍。相對更安全,但效率不高。
樂觀鎖:顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。
5.6 StampedLock
這是Java 8開始引進的一種讀寫鎖,讀的過程也允許獲取寫鎖后寫入。但需要一些代碼判斷是否有寫入。例:
package MultiThr;
import java.util.*;
import java.util.concurrent.locks.*;
public class Point {
public final StampedLock stampedLock = new StampedLock();
private double x;
private double y;
public void move(double deltaX, double deltaY){
long stamp = stampedLock.writeLock(); // 獲取寫鎖
try{
x += deltaX;
y += deltaY;
}finally{
stampedLock.unlockWrite(stamp); // 釋放寫鎖
}
}
public double distanceFromOrigin(){
long stamp = stampedLock.tryOptimisticRead();
double currentX = x;
double currentY = y;
if (!stampedLock.validate(stamp)){ // 檢查是否有其他寫鎖發生
stampedLock.readLock(); // 這是個悲觀鎖
try{
currentX = x;
currentY = y;
} finally{
stampedLock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY + currentY);
}
}
5.7 Concurrent集合與Atomic操作以及volatile關鍵字
首先應明白何為“線程安全”。
線程安全就是多線程訪問時,采用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可使用。
引用地址
Concurrent集合:由于默認類并非線程安全,所以調用時為了實現線程安全可以加鎖。但Java的并發機制已經為我們寫好了,即Concurrent集合,線程安全類。對照表如下:
interface
non-thread-safe
thread-safe
List
ArrayList
CopyOnWriteArrayList
Map
HashMap
ConcurrentHashMap
Set
HashSet / TreeSet
CopyOnWriteArraySet
Queue
ArrayDeque / LinkedList
ArrayBlockingQueue / LinkedBlockingQueue
Deque
ArrayDeque / LinkedList
LinkedBlockingQueue
volatile關鍵字:不加鎖也可以實現同步的一種機制,使得field可以被同其他線程同步。
例:
private volatile boolean done;
public boolean isDone(){ return done; }
public void setDone(){ done = true; }
6. 線程池
定義:能接受大量小任務并進行分發處理,使用ExecutorSerivce接口來表示。
ExecutorService有三個常用實現:
FixedThreadPool:固定大小的線程池
CachedThreadPool:根據任務動態調整的線程池
SingleThreadExecutor:僅單線執行的線程池
ScheduledThreadPool:定期反復執行的線程池
示例:
package MultiThr;
import java.util.concurrent.*;
public class ThrPool {
// 創建一個固定大小的線程池
ExecutorService es = Executors.newFixedThreadPool(4);
for(int i = 0; i <= 5; i++){
es.submit(new Task("" + i));
}
// 關閉線程池
es.shutdown(); // 使用shutdown關閉線程池的時候,線程池會等待當前任務完成
// shutdownNow(): 立即停止當前正在執行的任務
// awaitTermination()則會等待指定的時間讓線程池關閉
}
class Task implements Runnable{
private final String name;
public Task(String name){
this.name = name;
}
@Override
public void run(){
System.out.println("start task" + name);
try{
Thread.sleep(1000);
}catch(InterruptedException e){}
System.out.println("end task" + name);
}
}
6.1 動態調整的線程池
在簡介中說過CachedThreadPool可以實現這一功能。扒一下CachedThreadPool的源碼:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
所以可以這樣寫:
int min = 4 ;
int max = 10;
ExecutorService es = new ThreadPoolExecutor(min, max, 60L, TimeUnit.SECONDS, new SynchronousQueue());
6.2 定期反復執行的線程池
例如:每秒刷新證券價格的任務就可以通過這種線程池來執行。依然要通過Executors類來創建。
ScheduledExecutorService ses = Executors.newScheduledThreadPool(4);
一次性任務:
ses.schedule(new Task("one-time"), 1, TimeUnit.SECONDS);
以固定每3秒執行一次。FixedRate是指任務總是以固定時間間隔觸發,不管任務執行多長時間:
// 2秒后開始執行定時任務,每3秒執行一次
ses.scheduleAtFixedRate(new Task("fixed-rate"), 2, 3, TimeUnit.SECONDS);
每次任務執行間隔3秒。FixedDelay是指,上一次任務執行完畢后,等待固定的時間間隔,再執行下一次任務:
// 2秒后開始執行定時任務,每個任務之間間隔3秒
ses.scheduleWithFixedDelay(new Task("fixed-delay"), 2, 3, TimeUnit.SECONDS);
7. Runnable接口
在前作說過創建新任務可以實現Runnable接口。但這個接口有一個問題:是一個void方法,并無返回值,所以執行一些有返回值的任務時候就有所不便。
解決這個問題的方法是使用Callable接口。與Runnable相比多一個返回值。示例:
class Task implements Callable{
public String call() throws Exception{
return longTimeCalculation();
}
}
從以上代碼可以看出,Callable是一個泛型接口,<>當中標注要返回的類型,實現call方法可以返回指定的結果。
8. Future類型
一個Future類型的實例代表一個未來能獲取結果的對象,可以獲取異步執行的結果。ExecutorService.submit()方法可以返回一個Future()類型。
ExecutorService es = Executors.newFixedThreadPool(4);
// 定義任務
Callable task = new Task();
// 提交任務并獲得Future
Future f = es.submit(task);
// 從Future返回異步執行的結果
String s = f.get();
Future的方法有:
get() :獲取結果
get(long timeout, TimeUnit unit):獲取結果,但只等待指定的時間
cancel(boolean mayInterruptIfRunning): 取消當前任務
isDone():判斷任務是否完成
9. Fork/Join
其思想為x分法:如果一個任務比較大,那就把它分為x部分執行。
示例代碼(先粘貼,回去打):
public class Main {
public static void main(String[] args) throws Exception {
// 創建2000個隨機數組成的數組:
long[] array = new long[2000];
long expectedSum = 0;
for (int i = 0; i < array.length; i++) {
array[i] = random();
expectedSum += array[i];
}
System.out.println("Expected sum: " + expectedSum);
// fork/join:
ForkJoinTask task = new SumTask(array, 0, array.length);
long startTime = System.currentTimeMillis();
Long result = ForkJoinPool.commonPool().invoke(task);
long endTime = System.currentTimeMillis();
System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
}
static Random random = new Random(0);
static long random() {
return random.nextInt(10000);
}
}
class SumTask extends RecursiveTask {
static final int THRESHOLD = 500;
long[] array;
int start;
int end;
SumTask(long[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
// 如果任務足夠小,直接計算:
long sum = 0;
for (int i = start; i < end; i++) {
sum += this.array[i];
// 故意放慢計算速度:
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
return sum;
}
// 任務太大,一分為二:
int middle = (end + start) / 2;
System.out.println(String.format("split %d~%d ==> %d~%d, %d~%d", start, end, start, middle, middle, end));
SumTask subtask1 = new SumTask(this.array, start, middle);
SumTask subtask2 = new SumTask(this.array, middle, end);
invokeAll(subtask1, subtask2);
Long subresult1 = subtask1.join();
Long subresult2 = subtask2.join();
Long result = subresult1 + subresult2;
System.out.println("result = " + subresult1 + " + " + subresult2 + " ==> " + result);
return result;
}
}
10. ThreadLocal
在同一線程中傳遞同一對象。回去慢慢更。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的java如何写线程外部类_廖雪峰Java读书笔记(六)--多线程(或称并发)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我的名字和党员的党案的名字最后的一个字是
- 下一篇: java 将对象转_如何将Java对象转