2021年,記得元旦快了樂~~
文章目錄
- 線程與程序的差別
- 多線程應用場景
- 繼承方式建立多線程
-
- 同步和異步的概念?
-
- 同步
- 異步
- runnable接口建立多線程
- 使用匿名内部類方式建立多線程
- Callable方式建立多線程
- 線程池方式建立多線程
- 多線程常見API
-
- getId()
- getName()
- sleep()
- Thread.currentThread()
- stop()
- Thread()
- 守護線程與非守護線程
-
- 設定守護線程
- 多線程運作狀态
- join()方法作用
-
- 面試題順序執行T1,T2,T3
- 案例多線程分批處理資料
線程與程序的差別
-
什麼是應用程式?
可以執行的軟體,在一個應用程式中,都會有一個程序。
-
在程序中,程式代碼如何執行?
在程序中,一定有一個主線程,java中的main
-
什麼是線程?
程序中的一條執行順序/流程/路徑。
-
什麼是程序?
程序中有多個不同的執行路徑,程序是線程的集合,是正在執行中的程式,任務管理器中可以看見程序。
-
使用多線程的好處?
提高程式效率。
線程之間互不影響,因為線程都在自己獨立運作。
一個程序可中有多個線程,提高程式效率,提高使用者體驗。
-
注意事項
多線程并不提高單路徑執行速度,而是多路徑同時進行提高程式效率。
多線程應用場景
- 多線程下載下傳
- 爬蟲
- ajax異步上傳
- 分布式job,同一時刻執行多個任務排程
- mq
繼承方式建立多線程
建立線程有5種方式
- 繼承Thread類
- runnable接口
- 使用匿名内部類方式
- callable
- 線程池
繼承Thread接口,重寫(override)run方法,run方法就是線程需要執行的代碼。
class MyThread extends Thread{
@Override
public void run() {
for (int i =0 ; i< 30; i++){
System.out.println(i);
}
}
}
啟動線程,執行個體化類,調用start方法,如果調用run方法還是單獨的執行run方法。
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i =31 ; i< 60; i++){
System.out.println("main" + i);
}
}
啟動多線程,代碼不會從上往下執行。
同步和異步的概念?
同步
main函數中有方法①和方法②,每個方法都需要花費10秒時間,則需要20秒完成main函數。
代碼從上往下執行,被稱為同步。
同步中,main函數的方法之間存在依賴關系,存在執行順序,方法②等待方法①執行完畢後才能執行。
也叫做單線程。
異步
可以了解為多線程,方法①和方法②同時執行,每個方法都需要花費10秒時間,則需要10秒完成main函數。提高程式效率,線程間獨立,互不影響。
CPU排程時 異步中有執行權的概念,但在宏觀上看是同時進行的。
runnable接口建立多線程
建立類,實作runnable接口,重寫run方法
class MyThread2 implements Runnable{
public void run() {
for (int i =0 ; i< 30; i++){
System.out.println("run" + i);
}
}
}
啟動線程方式,接口作為參數傳遞給Thread類,調用start方法
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2();
Thread thread = new Thread(myThread2);
thread.start();
}
使用繼承方式建立線程還是使用runnable接口建立線程好?
使用接口方式,開發面向接口程式設計,繼承後不能再繼承,接口可以。
使用匿名内部類方式建立多線程
什麼是匿名内部類?
沒有名稱的内部類。
例子:
abstract class Person{
abstract void add();
}
public static void main(String[] args) {
Person person = new Person() {
void add() {
System.out.println("hello");
}
};
person.add();
}
線程中也是類似,執行個體化Thread類,runnable接口使用匿名内部類進行實作
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
public void run() {
System.out.println("Hello~");
}
});
thread.start();
}
Callable方式建立多線程
待更
線程池方式建立多線程
待更
多線程常見API
getId()
擷取線程的id,id唯一
class MyThread extends Thread{
@Override
public void run() {
for (int i =0 ; i< 30; i++){
System.out.println(getId()+ "run" + i);
}
}
}
主線程id如何擷取?
主線程id為1
任何一個程式都會有一個主線程,本質也是一個線程。
getName()
擷取線程名稱
主線程的name是main
子線程名稱預設是Thread-0,Thread-1,Thread-2…
sleep()
暫停
class MyThread extends Thread{
@Override
public void run() {
for (int i =0 ; i< 30; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getId()+ "run" + i);
}
}
}
Thread.currentThread()
擷取目前線程,在用Runnable接口的時候需要用這種方式擷取目前線程。
stop()
不安全,已過時
class MyThread extends Thread{
@Override
public void run() {
for (int i =0 ; i< 30; i++){
if (i==5){
stop();
}
System.out.println(getId()+ "run" + i);
}
}
}
Thread()
new Thread(new Runnable() {
public void run() {
System.out.println(1);
}
}, "線程名");
守護線程與非守護線程
什麼是守護線程?
和main函數相關。
守護線程和主線程一起銷毀。
如gc線程,垃圾回收機制中的單獨的一個線程,專門用來回收垃圾,主線程結束,守護線程一起結束。
什麼是非守護線程?
與main函數互不影響。
自己建立線程叫做使用者線程,如果主線程停止了,不會影響使用者線程,使用者線程也叫非守護線程。
非守護線程示範代碼:
public static void main(String[] args) {
new Thread(new Runnable() {
@lombok.SneakyThrows
public void run() {
for (int i = 0; i <= 30; i++) {
Thread.sleep(300);
System.out.println(i);
}
}
}, "線程名").start();
System.out.println("主線程執行完畢");
}
執行結果:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL2IjNwUDOxYTMxATMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
主線程結束和非守護線程沒有依賴關系。
設定守護線程
thread.setDaemon(true);
讓守護線程和主線程一起銷毀。
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@SneakyThrows
public void run() {
for (int i = 1000; i <= 3000; i++) {
System.out.println(i);
}
}
}, "線程名");
thread.setDaemon(true);
thread.start();
for (int i = 0; i <500; i++){
System.out.println(i);
}
System.out.println("主線程執行完畢");
}
多線程運作狀态
-
建立狀态
new Thread
-
就緒狀态
就緒狀态,等待cpu排程
-
運作狀态
擷取cpu排程權
-
死亡狀态
run方法執行完畢,或者stop方法
-
休眠(阻塞)狀态
調用了sleep方法或者wait()方法,運作狀态轉到阻塞狀态,再到就緒狀态
join()方法作用
現有線程A和線程B,線程A調用了線程B的join方法,A等待B線程執行完畢之後再繼續執行。也就是說線程A在調用B的join方法後,釋放了執行權,讓B先執行。
主線程讓子線程先執行,方法:在主函數中調用子線程的join方法,例子如下:
@SneakyThrows
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@SneakyThrows
public void run() {
for (int i = 1000; i <= 1500; i++) {
System.out.println(i);
}
System.out.println("子線程執行完畢!");
}
});
thread1.start();
thread1.join();
for (int i = 0; i <= 50; i++) {
System.out.println(i);
}
}
面試題順序執行T1,T2,T3
現有T1,T2,T3三個線程,怎麼保證T2在T1執行完後執行,T3在T2執行完後執行?
@SneakyThrows
public static void main(String[] args) {
final Thread thread1 = new Thread(new Runnable() {
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.println(i);
}
System.out.println("子線程1執行完畢!");
}
});
final Thread thread2 = new Thread(new Runnable() {
@SneakyThrows
public void run() {
thread1.join();
for (int i = 11; i <= 20; i++) {
System.out.println(i);
}
System.out.println("子線程2執行完畢!");
}
});
Thread thread3 = new Thread(new Runnable() {
@SneakyThrows
public void run() {
thread2.join();
for (int i = 21; i <= 30; i++) {
System.out.println(i);
}
System.out.println("子線程3執行完畢!");
}
});
thread1.start();
thread2.start();
thread3.start();
}
案例多線程分批處理資料
端口掃描
public void threadScanPort() {
ExecutorService executorService = Executors.newFixedThreadPool(100);
portCount.set(0);
int totalThreadNum = 100;
for (int n = 0; n <= totalThreadNum; n++) {
executorService.execute(new ScanHandler(n, totalThreadNum));
}
}
class ScanHandler implements Runnable {
//用于端口掃描的總共線程數
private int totalThreadNum;
// 線程号
private int threadNo;
public ScanHandler(int threadNo, int totalThreadNum) {
this.totalThreadNum = totalThreadNum;
this.threadNo = threadNo;
}
@Override
public void run() {
int startPort = Integer.parseInt(startPortField.getText());
int endPort = Integer.parseInt(endPortField.getText());
String host = targetIp.getText();
//startPort和endPort為成員變量,表示需要掃描的起止端口
for (int port = startPort + threadNo; port <= endPort; port = port + totalThreadNum) {
try {
Socket socket = new Socket();
socket.connect(new InetSocketAddress(host, port), 200);
socket.close();
String msg = "端口 " + port + " is open\n";
Platform.runLater(() -> {
taDisplay.appendText(msg);
});
} catch (IOException e) {
System.out.println(port + "is close");
}
portCount.incrementAndGet();//掃描的端口數+1
}
if (portCount.get() == (endPort - startPort + 1)) {
portCount.incrementAndGet();//加1,使得不再輸出下面的線程掃描結束的資訊
Platform.runLater(() -> {
taDisplay.appendText("\n----------------多線程掃描結束--------------------\n");
});
}
}
}