?
1.創建線程的方法:
案例:計算1-1000的整數和
實現Runnable接口
步驟:
1.創建一個實現了Runnable接口的類
2.實現類去實現Runnable中的抽象方法:run()
3.創建實現類的對象
4.將此對象作為參數傳遞到Thread類的構造器中,創建Thread類的對象
5.通過Thread類的對象調用start() ① 啟動線程 ②調用當前線程的run()–>調用了Runnable類型的target的run()
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保證線程安全且避免空指針問題public static AtomicLong result = new AtomicLong(0);static int count = 100000;public static CountDownLatch countDownLatch = new CountDownLatch(count);public static ExecutorService executor=Executors.newFixedThreadPool(10);public static void main(String[] args) throws InterruptedException, ExecutionException {long start = System.currentTimeMillis();Thread[] threads = new Thread[count];for (int i = 0; i < count; i++) {MyThread myThread = new MyThread(100);threads[i] = new Thread(myThread);threads[i].start();}countDownLatch.await();System.out.println(result.get()); // 使用 get 方法獲取 AtomicLong 的值System.out.println(System.currentTimeMillis() - start);}
}
class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法進行原子更新ThreadLearn.result.addAndGet(sum);ThreadLearn.countDownLatch.countDown();}
}
繼承Thread類
步驟:
1.創建一個繼承于Thread類的子類
2.重寫Thread類的run() --> 將此線程執行的操作聲明在run()中
3.創建Thread類的子類的對象
4.通過此對象調用start()執行線程
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保證線程安全且避免空指針問題public static AtomicLong result = new AtomicLong(0);static int count = 100000;public static CountDownLatch countDownLatch = new CountDownLatch(count);public static ExecutorService executor=Executors.newFixedThreadPool(10);public static void main(String[] args) throws InterruptedException, ExecutionException {long start = System.currentTimeMillis();Thread[] threads = new Thread[count];for (int i = 0; i < count; i++) {MyThread1 myThread = new MyThread1(100);threads[i] = new Thread(myThread);threads[i].start();}countDownLatch.await();System.out.println(result.get()); // 使用 get 方法獲取 AtomicLong 的值System.out.println(System.currentTimeMillis() - start);}
}
class MyThread1 extends Thread {private long count;MyThread1(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法進行原子更新ThreadLearn.result.addAndGet(sum);ThreadLearn.countDownLatch.countDown();}
}
實現Callable接口
步驟:
1.創建一個實現Callable的實現類
2.實現call方法,將此線程需要執行的操作聲明在call()中
3.創建Callable接口實現類的對象
4.將此Callable接口實現類的對象作為傳遞到FutureTask構造器中,創建FutureTask的對象
5.將FutureTask的對象作為參數傳遞到Thread類的構造器中,創建Thread對象,并調用start()
6.獲取Callable中call方法的返回值
package Reflection;import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保證線程安全且避免空指針問題public static AtomicLong result = new AtomicLong(0);static int count = 100000;public static CountDownLatch countDownLatch = new CountDownLatch(count);public static void main(String[] args) throws InterruptedException, ExecutionException {
// long start = System.currentTimeMillis();
// Thread[] threads = new Thread[count];
// for (int i = 0; i < count; i++) {
// MyThread1 myThread = new MyThread1(100);
// threads[i] = new Thread(myThread);
// threads[i].start();
// }
// countDownLatch.await();
// System.out.println(result.get()); // 使用 get 方法獲取 AtomicLong 的值
// System.out.println(System.currentTimeMillis() - start);FutureTask<Integer> futureTask=new FutureTask<>(new MyThread2(100));Thread thread=new Thread(futureTask);thread.start();Integer o = futureTask.get();System.out.println(o);}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法進行原子更新ThreadLearn.result.addAndGet(sum);ThreadLearn.countDownLatch.countDown();}
}class MyThread1 extends Thread {private long count;MyThread1(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法進行原子更新ThreadLearn.result.addAndGet(sum);ThreadLearn.countDownLatch.countDown();}
}class MyThread2 implements Callable<Integer> {private int count;public MyThread2(int count) {this.count=count;}@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}return sum;}
}
使用線程池
package Reflection;import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保證線程安全且避免空指針問題public static AtomicLong result = new AtomicLong(0);static int count = 100000;public static CountDownLatch countDownLatch = new CountDownLatch(count);//線程池public static ExecutorService executor=Executors.newFixedThreadPool(10);public static void main(String[] args) throws InterruptedException, ExecutionException {Future<Integer> submit = executor.submit(new MyThread2(100));Integer i=submit.get();System.out.println(i);}
}class MyThread2 implements Callable<Integer> {private int count;public MyThread2(int count) {this.count=count;}@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}return sum;}
}
2.統計多線程程序的運行時間
如果要統計多線程程序的執行時間,主線程等待所有子線程完成,那么總時間可能接近實際所有線程執行完畢的時間。所以可能需要確保主線程在所有子線程結束后才結束,然后計算整個程序的運行時間。
使用join方法
使用thread.join
,` 讓一個線程等待另一個線程執行完畢。在多線程編程中,有時我們需要確保某些線程在其他線程完成特定任務后再繼續執行,這時就可以使用 join() 方法來實現線程間的同步。
package Reflection;import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保證線程安全且避免空指針問題public static AtomicLong result = new AtomicLong(0);public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();int count = 10000; // 將線程數量和數組長度統一為 10000Thread[] threads = new Thread[count];for (int i = 0; i < count; i++) {MyThread myThread = new MyThread(100);threads[i] = new Thread(myThread);threads[i].start();}for (Thread thread : threads) {thread.join();}System.out.println(result.get()); // 使用 get 方法獲取 AtomicLong 的值System.out.println(System.currentTimeMillis() - start);}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法進行原子更新ThreadLearn.result.addAndGet(sum);}
}
使用CountDownLatch
CountDownLatch
是 Java 并發包 java.util.concurrent
中的一個同步輔助類,主要用于協調多個線程之間的執行順序。它可以讓一個或多個線程等待其他一組線程完成它們的操作后再繼續執行,從而確保程序的執行邏輯按照預期進行,避免出現數據不一致或邏輯錯誤的問題。
package Reflection;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 替代 Long,保證線程安全且避免空指針問題public static AtomicLong result = new AtomicLong(0);static int count = 100000;public static CountDownLatch countDownLatch = new CountDownLatch(count);public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();Thread[] threads = new Thread[count];for (int i = 0; i < count; i++) {MyThread myThread = new MyThread(100);threads[i] = new Thread(myThread);threads[i].start();}countDownLatch.await();System.out.println(result.get()); // 使用 get 方法獲取 AtomicLong 的值System.out.println(System.currentTimeMillis() - start);}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法進行原子更新ThreadLearn.result.addAndGet(sum);ThreadLearn.countDownLatch.countDown();}
}
執行結果:
CountDownLatch 的執行過程
1. 初始化階段
- 首先,需要創建一個
CountDownLatch
實例,在創建時要傳入一個初始計數值。這個計數值代表了需要等待完成的操作數量。例如在代碼中CountDownLatch countDownLatch = new CountDownLatch(count);
,這里的count
就是初始計數值,它表明有count
個任務需要完成,CountDownLatch
會將這個值存儲在內部作為計數器的初始狀態。
2. 任務線程啟動階段
- 通常會有一個或多個任務線程被創建并啟動去執行特定的任務。這些線程可能會并發地執行各自的任務,就像代碼里通過循環創建并啟動多個
Thread
實例,每個線程都去執行MyThread
類的run
方法中的任務。
3. 等待階段
- 有一個或多個線程(通常是主線程)會調用
CountDownLatch
的await()
方法。當調用這個方法時,調用線程會進入阻塞狀態,它會一直等待,直到CountDownLatch
的內部計數器值變為 0。這意味著它在等待所有任務都完成。
4. 任務完成與計數器遞減階段
- 每個任務線程在完成自己的任務后,會調用
CountDownLatch
的countDown()
方法。這個方法的作用是將CountDownLatch
的內部計數器值減 1。隨著越來越多的任務線程完成任務并調用countDown()
方法,計數器的值會不斷減小。
5. 喚醒等待線程階段
- 當
CountDownLatch
的內部計數器值減到 0 時,所有之前調用await()
方法并處于阻塞狀態的線程會被喚醒。這些線程會從await()
方法處繼續執行后續的代碼邏輯。
6. 后續處理階段
- 被喚醒的線程可以繼續執行后續的操作,比如代碼中主線程在被喚醒后會打印最終的計算結果以及程序執行所花費的時間。
主要方法
CountDownLatch(int count)
構造方法:用于創建一個CountDownLatch
實例,并初始化其內部計數器的值為count
。這個count
表示需要等待完成的操作數量,必須是一個正整數。void await()
方法:調用該方法的線程會進入阻塞狀態,直到CountDownLatch
的內部計數器值變為 0。如果在等待過程中當前線程被中斷,會拋出InterruptedException
異常。boolean await(long timeout, TimeUnit unit)
方法:調用該方法的線程會等待一段時間,最多等待timeout
時間(由unit
指定時間單位)。如果在這段時間內計數器值變為 0,則線程會被喚醒并返回true
;如果超過指定時間計數器值仍不為 0,則線程會被喚醒并返回false
。同樣,如果在等待過程中線程被中斷,會拋出InterruptedException
異常。void countDown()
方法:該方法會將CountDownLatch
的內部計數器值減 1。當計數器值減到 0 時,所有正在等待的線程會被喚醒。如果計數器值已經為 0,調用該方法不會產生任何效果。long getCount()
方法:該方法用于返回CountDownLatch
當前的計數器值。可以通過這個方法來查看還有多少個任務未完成。
3.如何解決多線程對共享變量操作的線程安全問題
package Reflection;public class ThreadLearn {public static long result ;public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10000; i++) {MyThread myThread = new MyThread(1000);Thread thread = new Thread(myThread);thread.start();}Thread.sleep(10*1000);System.out.println(result);}}
class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum+=i;}ThreadLearn.result+=sum;System.out.println(sum);}
}
執行結果:
??
代碼功能概述
這段 Java 代碼主要的功能是創建 10000 個線程,每個線程會計算從 0 到 1000 的整數之和,然后將這個和累加到 ThreadLearn
類的靜態變量 result
中。最后,主線程等待 10 秒后輸出 result
的值。
代碼存在的問題
- 線程安全問題:
ThreadLearn.result
是一個共享變量,多個線程同時對其進行寫操作(ThreadLearn.result += sum;
),這會導致數據競爭(Data Race)問題,最終的result
值可能是錯誤的。 - 等待時間不確定性:使用
Thread.sleep(10 * 1000)
來等待所有子線程完成,這種方式不夠可靠,因為不同的機器性能不同,可能會導致有些線程還未執行完,主線程就已經輸出結果。
你提供的代碼在多線程環境下存在線程安全問題,主要是因為多個線程同時對靜態變量 ThreadLearn.result
進行寫操作,這可能會導致數據競爭和不一致的結果。下面為你介紹幾種解決該問題的方法。
方法一:使用 synchronized 關鍵字
synchronized
關鍵字可以用來修飾方法或代碼塊,保證同一時刻只有一個線程能夠訪問被修飾的代碼,從而避免多線程對共享資源的并發訪問問題。
package Reflection;public class ThreadLearn {public static long result;public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10000; i++) {MyThread myThread = new MyThread(1000);Thread thread = new Thread(myThread);thread.start();}Thread.sleep(10 * 1000);System.out.println(result);}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 synchronized 塊保證線程安全synchronized (ThreadLearn.class) {ThreadLearn.result += sum;}System.out.println(sum);}
}
解釋:在 run
方法中,使用 synchronized (ThreadLearn.class)
對 ThreadLearn.result += sum;
這行代碼進行同步,這樣同一時刻只有一個線程能夠執行該代碼塊,從而保證了對 result
變量的線程安全訪問。
執行結果:
方法二:使用 AtomicLong 類
AtomicLong
是 Java 提供的一個原子類,它提供了一些原子操作方法,可以保證對長整型變量的原子性更新,避免了使用 synchronized
帶來的性能開銷。
package Reflection;import java.util.concurrent.atomic.AtomicLong;public class ThreadLearn {// 使用 AtomicLong 代替 longpublic static AtomicLong result = new AtomicLong(0);public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10000; i++) {MyThread myThread = new MyThread(1000);Thread thread = new Thread(myThread);thread.start();}Thread.sleep(10 * 1000);System.out.println(result.get());}
}class MyThread implements Runnable {private int count;public MyThread(int count) {this.count = count;}@Overridepublic void run() {int sum = 0;for (int i = 0; i <= count; i++) {sum += i;}// 使用 AtomicLong 的 addAndGet 方法進行原子更新result.addAndGet(sum);System.out.println(sum);}
}
解釋:將 ThreadLearn
類中的 result
變量類型改為 AtomicLong
,并使用 addAndGet
方法來更新 result
的值。addAndGet
方法是原子操作,能夠保證在多線程環境下對 result
的更新是線程安全的。
執行結果:
。