東哥手把手帶你刷二叉樹(第三期)
阿新 • • 發佈:2020-11-15
多執行緒
1. 執行緒、程序簡介
程式是指令和資料的有序集合,其本身沒有任何執行的含義,是一個靜態的概念。
程序是執行程式的一次執行過程,是一個動態的概念。是系統資源分配的單位。 程序時系統分配的。
通常一個程序中包含多個執行緒,一個程序至少有一個執行緒。執行緒是CPU排程和執行的單位。
核心概念:
- 執行緒就是獨立的執行路徑
- 在程式執行時,即使沒有自己建立執行緒,後臺也會有多個執行緒,如主執行緒,gc執行緒(垃圾回收執行緒);
- main()被稱為主執行緒,是系統的入口,用於執行整個程式
- 在一個程序中,如果開闢了多個執行緒,執行緒的執行由排程器安排排程,排程器是與作業系統緊密相關的,先後順序是不能人為干預的。
- 對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制
- 執行緒會帶來額外的開銷,如cpu排程時間,併發控制開銷
- 每個執行緒在自己的工作記憶體互動,記憶體控制不當會造成資料的不一致
2.執行緒建立
三種建立方式:
- 繼承Thread類
- 實現Runnable介面
- 實現Callable介面
一、Thread
- 自定義執行緒類繼承Thread類
- 重寫run()方法,編寫執行緒實體
- 建立執行緒物件,呼叫start()方法啟動執行緒
public class TestThread1 extends Thread{ @Override public void run() { //run方法執行緒體 for (int i = 0; i < 20; i++) { System.out.println("我在看程式碼--"+i); } } public static void main(String[] args) { TestThread1 testThread1 = new TestThread1(); // 呼叫run()方法是先執行run()方法,在執行main()方法 // testThread1.run(); //呼叫start()方法,是main()方法與start()方法交替執行輸出的時候也是交替輸出。 //所以不能呼叫run()方法進行執行執行緒 testThread1.start(); for (int i = 0; i < 900; i++) { System.out.println("我在學習多執行緒-"+i); } } }
使用多執行緒以及IO進行網路圖片的下載
package com.lzp.thread; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; public class TestThread2 extends Thread{ private String url; private String name; public TestThread2(String url, String name){ this.name = name; this.url = url; } @Override public void run() { WebDownloader webDownloader = new WebDownloader(); webDownloader.download(this.url,this.name); System.out.println("下載了" + this.name); } public static void main(String[] args) { TestThread2 t1 = new TestThread2("https://www.baidu.com/link?url=R82Nt0nRYu128ZqFYHOIt7v-xdFg7wNJodcc-LQSgePd2m2Za5_JS8PR8HB0eBtDlE2K1o-KWY8M_D6E6_x1yKOjUucaFqgXOdT01Se97ukTd9F3W4Ja0pIHGNvZL3PE1f0h3wlkvxo9pKWEgGv5RnaWEgerJBTw3MqOTiQquQpsN-_jvFjm-bcXiUd45QvJATU_UMGXmDGGuG0n7OQZ4U2IxN16Aj2vzlAWr569iuq1LTPCXYXDz0nDZdrKmHEga6pfuhq8yBAuMe1AC2j2AI8LT5a9u7ADAGYkspyn0MLPXUFqFixWmt2IjKpXMtAhM6urUaQ2BvppLLT0-OJhozH8Kov_YLNeLDB5EVhGoJICwSd9Da4af4fVZJ5tQiml9YwmXz6dnNd1TsPEJur09Dy23gbjFQqMHZFkHNJxB1Hmm-0ZzMx6_JIok-8hy6EkblRs9c-BGY_drTXzMw12RK84AFe0WrG0p_rU6b0ZpxdPF0Ch9FbN_lJ-PW_HuRHUHOCZE0Wc587NmWF7QiYiEul5QMLlLMLi8X2jgn2sp7BxyWWnyR9bLI3NXZK2ypgz-pcvmKn3yZ0Xm3BJkUGKjz1SWUXdsm8N4ChMplvc14Rd2K7crLPXPADo_VC8yMwiq1WUmW0MCQh2-6fE9K0eBjlrazZGm9dNbyQhqsj3-_W&timg=&click_t=1605149513263&s_info=1519_762&wd=&eqid=b5ee26b700000735000000055faca348","1.jpg"); TestThread2 t2 = new TestThread2("https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=3083984867,3167144390&fm=26&gp=0.jpg","2.jpg"); TestThread2 t3 = new TestThread2("https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=645805497,3565410143&fm=26&gp=0.jpg","3.jpg"); t1.start(); t2.start(); t3.start(); } } class WebDownloader{ public void download(String url, String name){ try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("download出現問題"); } } }
根據輸出,圖片並不是按順序下載下來的,而是同時進行下載的,哪個先下載完那個先進行輸出。
二、Runnable
- 定義MyRunnable類實現Runnable介面
- 實現run()方法,編寫執行緒執行體
- 建立執行緒物件,呼叫start()方法
public class TestThread3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我在學習+" + i);
}
}
public static void main(String[] args) {
TestThread3 testThread3 = new TestThread3();
//注意啟動執行緒的方法
new Thread(testThread3).start();
for (int i = 0; i < 900; i++) {
System.out.println("我在玩遊戲+"+i);
}
}
}
同樣進行網路圖片下載
package com.lzp.thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class TestThread4 implements Runnable{
private String url;
private String name;
public TestThread4(String url, String name){
this.name = name;
this.url = url;
}
@Override
public void run() {
WebDownloader1 webDownloader = new WebDownloader1();
webDownloader.download(this.url,this.name);
System.out.println("下載了" + this.name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=2516762561,2728702104&fm=26&gp=0.jpg","4.jpg");
TestThread2 t2 = new TestThread2("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3003984691,41310498&fm=26&gp=0.jpg","5.jpg");
TestThread2 t3 = new TestThread2("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3142750253,2237652291&fm=26&gp=0.jpg","6.jpg");
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
}
}
class WebDownloader1{
public void download(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("download出現問題");
}
}
}
總結:
- 繼承Thread類
- 子類繼承Thread類具備多執行緒能力
- 啟動執行緒:子類物件.start()
- 不建議使用:避免OOP單繼承侷限性
- 實現Runnable介面
- 實現介面Runnable具有多執行緒能力
- 啟動執行緒:傳入目標物件+Thread物件.start()
- 推薦使用:避免單繼承侷限性,靈活方便,方便同一個物件被多個執行緒使用
多個執行緒操作同一個資源:
package com.lzp.thread;
//多個執行緒同時操作一個物件
//買火車票的例子
//問題是:多個執行緒操作同一個資源的情況下,執行緒不安全,資料紊亂
public class TestThread5 implements Runnable{
private int ticketNums;
public TestThread5(int ticketNums){
this.ticketNums = ticketNums;
}
@Override
public void run() {
while(ticketNums>0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "張票");
}
}
public static void main(String[] args) {
TestThread5 testThread5 = new TestThread5(10);
new Thread(testThread5,"小明").start();
new Thread(testThread5,"老師").start();
new Thread(testThread5,"黃牛").start();
}
}
多執行緒模擬龜兔賽跑
package com.lzp.thread;
//模擬龜兔賽跑
public class Race implements Runnable{
private static String winner;
@Override
public void run() {
for(int i=0;i<=100;i++){
//模擬兔子休息
// if(Thread.currentThread().getName().equals("兔子") && i%10==0){
// try {
// Thread.sleep(5);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
boolean flag = isGameOver(i);
if(flag){
break;
}
System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
}
}
public boolean isGameOver(int step){
if (winner != null) {
return true;
}else {
if(step >= 100){
winner = Thread.currentThread().getName();
System.out.println("winner is"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"烏龜").start();
}
}
三、Callable
- 實現Callable介面,需要返回值型別
- 重寫call方法,需要丟擲異常
- 建立目標物件
- 建立執行服務: ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交執行:Future
result1 = ser.submit(t1); - 獲取結果:boolean r1 = result.get();
- 關閉服務:ser.shutdownNow();
可以定義返回值
可以丟擲異常
package com.lzp.thread;
import java.util.concurrent.*;
public class TestCallable implements Callable<Boolean> {
private String url;
private String name;
public TestCallable(String url, String name){
this.name = name;
this.url = url;
}
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.download(this.url,this.name);
System.out.println("下載了" + this.name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=226931949,1036395346&fm=26&gp=0.jpg","7.jpg");
TestCallable t2 = new TestCallable("https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=661299091,4083766848&fm=26&gp=0.jpg","8.jpg");
TestCallable t3 = new TestCallable("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=3246233636,10654504&fm=26&gp=0.jpg","9.jpg");
//建立執行服務:
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交執行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
//獲取結果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
//關閉服務
ser.shutdown();
}
}
Lamda表示式
使用lamda表示式可以:
- 避免匿名內部類定義過多
- 可以讓程式碼看起來更簡潔
- 去掉了沒有意義的程式碼,只留下核心的邏輯
函式式介面定義:
- 任何介面,如果只包含唯一一個抽象方法,那麼他就是一個函式式介面。
- 對於函式式介面,可以通過lamda表示式來建立該介面的物件。
package com.lzp.lambda;
public class TestLambda1 {
//3.靜態內部類
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda2");
}
}
public static void main(String[] args) {
//實現外部類的程式碼
ILike like = new Like();
like.lambda();
//實現靜態內部類的程式碼
like = new Like2();
like.lambda();
//4.區域性內部類
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}
like = new Like3();
like.lambda();
//5.匿名內部類,沒有類的名稱,必須藉助介面或者父類
like = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda4");
}
};
like.lambda();
//6.lambda表示式簡化類
like = ()->{
System.out.println("I like lambda5");
};
like.lambda();
}
}
//1.定義一個函式式介面
interface ILike{
void lambda();
}
//2.實現類
class Like implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda1");
}
}
package com.lzp.lambda;
public class TestLambda2 {
public static void main(String[] args) {
ILove love = null;
love = (int a)->{
System.out.println("I Love you " + a);
};
//簡化,可以去掉型別,
//可以去掉小括號,前提是隻有一個引數
//還可以去掉大括號,前提是表示式裡只有一行程式碼
love = (a)->{
System.out.println("I Love you " + a);
};
love.love(520);
}
}
interface ILove{
void love(int a);
}
3.執行緒狀態
五大狀態:
- 建立狀態
- 就緒狀態
- 阻塞狀態
- 執行狀態
- 死亡狀態
執行緒方法:
1.停止執行緒:
package com.lzp.state;
//測試stop
//1.建議征程停止,利用次數,不建議死迴圈
//2.建議使用標誌位,設定一個標誌位
//3.不要使用stop或者destroy等過時或者JDK不建議使用的方法
public class TestStop implements Runnable{
private boolean flag = true;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("Thread "+ i++);
}
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for(int i=0;i<500;i++){
System.out.println("main "+i);
if(i==400){
testStop.flag = false;
System.out.println("執行緒停止了");
}
}
}
}
2.執行緒休眠(sleep):
- sleep指定當前執行緒阻塞的毫秒數
- sleep存在異常InterruptedException
- sleep時間達到後執行緒進入就緒狀態
- sleep可以模擬網路延時、倒計時等
- 每一個物件都有一個鎖,sleep不會釋放