Java多線程
1、引入
1、何時需要多線程:
程序需要同時執行兩個或多個任務。
程序需要實現一些需要等待的任務時,如用戶輸入、文件讀寫操作、網絡操作、搜索等。
需要一些后台運行的程序時。
2、多線程
Java語言的JVM允許程序運行多個線程,它通過java.lang.Thread 類來體現。
Thread類的特性 :
每個線程都是通過某個特定Thread對象的run()方法來完成操作的,經常把run()方法的主體稱為線程體
通過該Thread對象的start()方法來啟動這個線程,而非直接調用run()
線程的創建和使用Thread類
2、API中創建線程的方式
1、繼承Thread類
1、方法介紹
| 方法名 | 說明 |
|---|---|
| void run() | 在線程開啟后,此方法將被調用執行 |
| void start() | 使此線程開始執行,Java虛擬機會調用run方法() |
2、實現步驟
1、 定義子類繼承Thread類。
2、 子類中重寫Thread類中的run方法。
3、 創建Thread子類對象,即創建了線程對象。
4、 調用線程對象start方法:啟動線程,調用run方法。
注意
1、想要啟動多線程,必須調用start方法。,只有使用start方法才能啟動一個線程.
2、如果自己手動調用run()方法,那么就只是普通方法,沒有啟動多線程模式。
3、run()方法由JVM調用,什么時候調用,執行的過程控制都有操作系統的CPU調度決定。
4、一個線程對象只能調用一次start()方法啟動,如果重復調用了,則將拋出以上的異常“IllegalThreadStateException”。
3、案例
public class Demo1 {
public static void main(String[] args) {
//創建線程對象
PrintWorld printWorld = new PrintWorld();
PrintThread printThread = new PrintThread();
//兩個線程開始工作,開始爭奪系統資源
printWorld.start();
printThread.start();
}
}
//打印:你好世界
class PrintWorld extends Thread{
@Override
public void run() {
//要將這個線程執行的代碼寫入run方法中
for (int i = 1; i <= 100; i++) {
System.out.println("**第"+i+"次:你好世界!");
}
}
}
//打印:線程你好
class PrintThread extends Thread{
@Override
public void run() {
//要將這個線程執行的代碼寫入run方法中
for (int i = 1; i <= 100; i++) {
System.out.println("--第"+i+"次:線程你好!");
}
}
}
2、實現Runnable接口
1、方法介紹
| 方法名 | 說明 |
|---|---|
| Thread(Runnable target) | 分配一個新的Thread對象 |
| Thread(Runnable target, String name) | 分配一個新的Thread對象 |
2、實現步驟
1、 定義子類,實現Runnable接口。
2、 子類中重寫Runnable接口中的run方法。
3、 通過Thread類含參構造器創建線程對象。
4、 將Runnable接口的子類對象作為實際參數傳遞給Thread類的構造器中。
5、 調用Thread類的start方法:開啟線程,調用Runnable子類接口的run方法。
3、案例
練習: 利用實現接口的方式創建兩個線程,其中一個線程 打印 1000以內的奇數 另一個線程打印 1000以內所有的偶數
public class Demo2 {
public static void main(String[] args) {
//創建兩個任務類對象
EvenNumber evenNumber = new EvenNumber();
OddNumber oddNumber = new OddNumber();
//創建兩個線程對象,Thread類 將兩個任務對象作為參數 傳遞給線程對象 讓線程對象 去執行這個任務
Thread t1 = new Thread(oddNumber);
Thread t2 = new Thread(evenNumber);
//啟動兩個線程
t1.start();
t2.start();
}
}
//打印1000以內奇數
class OddNumber implements Runnable{
@Override
public void run() {
//這個任務類 要完成的任務代碼
for (int i = 1; i <=1000 ; i+=2) {
System.out.println("=============="+i+"===============");
}
}
}
//打印1000以內偶數
class EvenNumber implements Runnable{
@Override
public void run() {
//這個任務類 要完成的任務代碼
for (int i = 0; i <=1000 ; i+=2) {
System.out.println("+++++++++++++++"+i+"+++++++++++++++++++");
}
}
}
3、實現Callable接口
1、方法介紹
| 方法名 | 說明 |
|---|---|
| V call() | 計算結果,如果無法計算結果,則拋出一個異常 |
2、FutureTask類中的方法
構造方法:
FutureTask(Callable<V> callable)創建一個 FutureTask,一旦運行就執行給定的 CallableV
成員方法:
get()如有必要,等待計算完成,然后獲取其結果
3、實現步驟
1、定義一個子類MyCallable實現Callable接口
2、在子類MyCallable中重寫call()方法
3、創建子類MyCallable的對象
4、創建Future的實現類FutureTask對象,把MyCallable對象作為構造方法的參數
5、創建Thread類的對象,把FutureTask對象作為構造方法的參數
6、啟動線程
7、再調用get方法,就可以獲取線程結束之后的結果。
4、案例
//案例一:
package com.offcn.day21;
import java.io.FileNotFoundException;
import java.util.concurrent.*;
public class Demo9 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//創建兩個任務類對象
OddNumber oddNumber = new OddNumber();
EvenNumber evenNumber = new EvenNumber();
// Callable接口的實現類對象 是不能直接提交給線程對象的必須要作為參數傳遞給 FutureTask 再將FutureTask對象作為參數提交線程對象
FutureTask<Integer> futureTask1 = new FutureTask<Integer>(oddNumber);
FutureTask<Integer> futureTask2 = new FutureTask<Integer>(evenNumber);
Thread t1 = new Thread(futureTask1);
Thread t2 = new Thread(futureTask2);
t1.start();
t2.start();
Integer integer = futureTask1.get();
Integer integer1 = futureTask2.get();
System.out.println(integer+ integer1);//這個結果 必須要等到兩個線程都結束才能獲取
// 方法二
// ExecutorService executorService = Executors.newFixedThreadPool(2);
// Future<Integer> submit = executorService.submit(ji);
// Future<Integer> submit1 = executorService.submit(ou);
// Integer integer = submit.get();
// Integer integer1 = submit1.get();
// System.out.println(integer + integer1);
// executorService.shutdown();
}
}
class OddNumber implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <=100 ; i+=2) {
sum += i;
System.out.println(i);
}
return sum;
}
}
class EvenNumber implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 2; i <=100 ; i+=2) {
sum += i;
System.out.println(i);
}
return sum;
}
}
案例: 創建 兩個任務對象:
任務對象1: 任務是 拼接 所有的小寫字母 abcdefg .....z
任務對象2: 任務是 拼接 所有的大寫字母 A....Z
最后將兩個字符串進行拼接 打印輸出
import java.util.concurrent.*;
public class Demo4 {
public static void main(String[] args) throws Exception{
// 采用第一種方式
LowerCase lowerCase = new LowerCase();
UpperCase upperCase = new UpperCase();
//線程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//為線程池提交任務
Future<StringBuilder> submit1 = pool.submit(lowerCase);
Future<StringBuilder> submit2 = pool.submit(upperCase);
StringBuilder stringBuilder1 = submit1.get();
StringBuilder stringBuilder2 = submit2.get();
System.out.println(stringBuilder1.append(stringBuilder2));
pool.shutdown();
// 方式二
// FutureTask<StringBuilder> f1 = new FutureTask<>(lowerCase);
// FutureTask<StringBuilder> f2 = new FutureTask<>(upperCase);
//
// //創建兩個線程
// Thread t1 = new Thread(f1);
// Thread t2 = new Thread(f2);
//
// t1.start();
// t2.start();
//
// //接收 兩個任務完成之后的 結果
// StringBuilder stringBuilder1 = f1.get();
// StringBuilder stringBuilder2 = f2.get();
//
// StringBuilder append = stringBuilder1.append(stringBuilder2);
// System.out.println(append);
}
}
class LowerCase implements Callable<StringBuilder>{
@Override
public StringBuilder call() throws Exception {
StringBuilder stringBuilder = new StringBuilder();
for (char i = 'a'; i <= 'z' ; i++) {
stringBuilder.append(i);
}
return stringBuilder;
}
}
class UpperCase implements Callable<StringBuilder>{
@Override
public StringBuilder call() throws Exception {
StringBuilder stringBuilder = new StringBuilder();
for (char i = 'A'; i <= 'Z' ; i++) {
stringBuilder.append(i);
}
return stringBuilder;
}
}
4、繼承方式和實現方式的聯系與區別
1、區別
1、繼承Thread:線程代碼存放Thread子類run方法中。
2、實現Runnable、Callable:線程代碼存在接口的子類的run方法。
2、實現方式的好處
1、避免了單繼承的局限性
2、多個線程可以共享同一個接口實現類的對象,非常適合多個相同線程來處理同一份資源。
3、匿名內部類創建線程
1、繼承Thread類的形式
案例:
public class Demo11 {
public static void main(String[] args) {
//局部內部類
//定義一個類 繼承 Thread 類
//父類引用指向子類對象
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}.start();
}
}
2、實現Runnable接口形式
案例:
public class Demo12 {
public static void main(String[] args) {
//創建任務類對象
//接口引用指向實現類對象
//創建線程對象
// new Thread( new Runnable(){
//
// @Override
// public void run() {
// for (int i = 0; i < 100; i++) {
// System.out.println(i);
// }
// }
// }).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}).start();
//寫成 lambda表達式
// new Thread(()->{
// for (int i = 0; i < 100; i++) {
// System.out.println(i);
// }
// }).start();
}
}
3、實現Callable接口形式
案例:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Demo11 {
public static void main(String[] args) {
//創建任務對象
//接口引用指向實現類對象
new Thread(new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
return null;
}
})).start();
}
}
4、線程池創建線程
1、概述
1、概念
提到池,大家應該能想到的就是水池。水池就是一個容器,在該容器中存儲了很多的水。
那么什么是線程池呢?
線程池也是可以看做成一個池子,在該池子中存儲很多個線程。
2、意義
系統創建一個線程的成本是比較高的,因為它涉及到與操作系統交互,
當程序中需要創建大量生存期很短暫的線程時,頻繁的創建和銷毀線程對系統的資源消耗有可能大於業務處理是對系統資源的消耗,這樣就有點"舍本逐末"了。
針對這一種情況,為了提高性能,我們就可以采用線程池。
線程池在啟動的時候,會創建大量空閑線程;
當我們向線程池提交任務的時,線程池就會啟動一個線程來執行該任務。
等待任務執行完畢以后,線程並不會死亡,而是再次返回到線程池中稱為空閑狀態,等待下一次任務的執行。
2、創建線程池
1、創建線程池——Executors
1、概述
概述 : JDK對線程池也進行了相關的實現,在真實企業開發中我們也很少去自定義線程池,而是使用JDK中自帶的線程池。
我們可以使用Executors中所提供的靜態方法來創建線程池
1.static ExecutorService newCachedThreadPool() 創建一個默認的線程池
2.static newFixedThreadPool(int nThreads) 創建一個指定最多線程數量的線程池
3.static ExecutorService newSingleThreadExecutor()創建一個使用從無界隊列運行的單個工作線程的執行程序。
ExecutorService :
<T> Future<T> submit(Callable<T> task)
提交值返回任務以執行,並返回代表任務待處理結果的Future。
Future<?> submit(Runnable task)
提交一個可運行的任務執行,並返回一個表示該任務的未來
案例:
//案例1:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo5 {
public static void main(String[] args) {
//獲取一個線程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//只有一條線程
Runnable runnable1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的====================="+i);
}
}
};
Runnable runnable2 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的!!!!!!!!!!!!!!!!!!!!!!!!!!!"+i);
}
}
};
executorService.submit(runnable1);//提交了一次
executorService.submit(runnable2);
executorService.shutdown();
}
}
//案例2:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo6 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
//只有一條線程
Runnable runnable1 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的====================="+i);
}
}
};
Runnable runnable2 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的!!!!!!!!!!!!!!!!!!!!!!!!!!!"+i);
}
}
};
Runnable runnable3 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的::::::::::::::::::::::::::::::::"+i);
}
}
};
Runnable runnable4 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"打印的***********************************"+i);
}
}
};
executorService.submit(runnable1);//提交了一次
executorService.submit(runnable2);
executorService.submit(runnable3);
executorService.submit(runnable4);
executorService.shutdown();
}
}
2、創建線程池——ThreadPoolExecutor
1、概述
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(核心線程數量,最大線程數量,空閑線程最大存活時間,任務隊列,創建線程工廠,任務的拒絕策略);
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
創建一個新 ThreadPoolExecutor給定的初始參數。
2、TheadPoolExecutor原理介紹以及任務拒絕策略介紹
2.1、參數詳解

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize: 核心線程的最大值,不能小於0
maximumPoolSize:最大線程數,不能小於等於0,maximumPoolSize >= corePoolSize
keepAliveTime: 空閑線程最大存活時間,不能小於0
unit: 時間單位
workQueue: 任務隊列,不能為null
threadFactory: 創建線程工廠,不能為null
handler: 任務的拒絕策略,不能為null
2.2、線程池-非默認任務拒絕策略
概述
概述: RejectedExecutionHandler是jdk提供的一個任務拒絕策略接口,它下面存在4個子類。
ThreadPoolExecutor.AbortPolicy: 丟棄任務並拋出RejectedExecutionException異常。是默認的策略。
ThreadPoolExecutor.DiscardPolicy: 丟棄任務,但是不拋出異常 這是不推薦的做法。
ThreadPoolExecutor.DiscardOldestPolicy: 拋棄隊列中等待最久的任務 然后把當前任務加入隊列中。
ThreadPoolExecutor.CallerRunsPolicy: 調用任務的run()方法繞過線程池直接執行。
注意事項
注意事項: 明確線程池對多可執行的任務數 = 隊列容量 + 最大線程數
案例:
//案例
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo10 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3 , 5 , 1L , TimeUnit.SECONDS , new ArrayBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy()
);
//通過循環 來給線程池提交任務
for (int i = 1; i <= 9 ; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在辦理業務");
}
});
}
threadPoolExecutor.shutdown();
}
}
