- 線程就是獨立的執行路徑
- 在程序運行時,即使沒有自己創建線程,後臺也會有多個線程,如主線程、gc線程
- main()稱之爲主線程,爲系統的入口,用於執行整個程序
- 在一個進程中,如果開闢了多個線程,線程的運行由調度器安排調度,調度器是與操作系統密切相關的,先後順序是不能人爲干預的
- 對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制
- 線程會帶來額外的開銷,如CPU調度時間,併發控制開銷
- 每個線程在自己的工作內存交互,內存控制不當會造成數據不一致
介紹
Java 給多線程編程提供了內置的支持。 一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。
一個進程包括由操作系統分配的內存空間,包含一個或多個線程。一個線程不能獨立的存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守護線程都結束運行後才能結束。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6Wf0bDCR-1593089275811)(C:\Users\Tangs\AppData\Roaming\Typora\typora-user-images\1593048108216.png)]
Process與Thread
程序是指令和數據的有序集合,其本身沒有任何運行的含義,是一個靜態的概念。
進程則是執行程序的一次執行過程,他是一個動態的概念。是系統資源分配的單位。
通常一個進程中可以包含若干個線程。線程是CPU調度和執行的基本單位。
**注意:**很多多線程是模擬出來的,真正的多線程指有多個CPU,即多核,如服務器。如果是模擬出來的多線程,即在一個CPU的情況下,在同一時間點,CPU只能執行一個代碼,因爲切換的很快,所以認爲是同時執行。
線程創建
- Thread class 繼承Thread類
- Runnable接口 實現Runnable接口
- Callable接口 實現Callable接口
Thread類
- 自定義線程類繼承Thread類
- 重寫**run()**方法,編寫線程執行體
- 創建線程對象,調用**start()**方法啓動線程
public class TestThread extends Thread{
@Override
public void run(){
//run方法線程體
for(int i = 0; i < 20; i++){
System.out.println("我在看代碼---"+i);
}
}
public static void main(String[] args){
//main線程,主線程
//創建一個線程對象
TestThread testThread = new TestThread();
//調用start()方法開啓線程
testThread.start();
for(int i = 0; i < 20; i++){
System.out.println("多線程執行"+i);
}
}
}
實例:線程同時下載圖片
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class ThreadTest extends Thread {
private String url; //圖片下載路徑
private String name; //圖片名字
public ThreadTest(String url, String name) {
this.url = url;
this.name = name;
}
//下載圖片線程執行體
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下載的文件名爲:"+name);
}
public static void main(String[] args) {
ThreadTest t1 = new ThreadTest("https://img-blog.csdn.net/20161120091317312", "1.jpg");
ThreadTest t2 = new ThreadTest("https://img-blog.csdn.net/20161120091128267", "2.jpg");
ThreadTest t3 = new ThreadTest("https://img-blog.csdn.net/20161120091439735", "3.jpg");
t1.start();
t2.start();
t3.start();
}
}
//下載器
class WebDownloader{
// 下載方法
public void downloader(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
Runnable 接口
- 創建Runnable接口的實現類對象
- 創建線程對象,通過線程對象來開啓我們的線程(代理模式)
public class TestThread implements Runnable{
@Override
public void run(){
//run方法線程體
for(int i = 0; i < 20; i++){
System.out.println("我在看代碼---"+i);
}
}
public static void main(String[] args){
//main線程,主線程
//創建runnable接口的實現類對象
TestThread testThread = new TestThread();
//創建線程對象,通過線程對象來開啓我們的線程(代理)
new Thread(testThread).start();
for(int i = 0; i < 20; i++){
System.out.println("多線程執行"+i);
}
}
}
繼承Thread類與實現Runnable接口對比
繼承Thread類
- 子類繼承Thread類具備多線程能力
- 啓動線程:子類對象.start();
- 不建議使用:避免OOP單繼承侷限性
實現Runnable接口
- 實現接口Runnable具有多線程能力
- 啓動線程:傳入目標對象+Thread對象.start();
- 推薦使用:避免單繼承的侷限性,方便靈活,方便同一對象被多個線程使用
注意:當同一資源被多個線程利用時,會出現錯誤。例如以下購買車票程序,多人購買到同一張車票
public class ThreadTest1 implements Runnable {
//票數
private int ticketNums = 10;
@Override
public void run() {
while (true){
if(ticketNums <= 0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"獲得了第"+ticketNums--+"張票");
}
}
public static void main(String[] args) {
ThreadTest1 threadTest1 = new ThreadTest1();
new Thread(threadTest1, "小米").start();
new Thread(threadTest1, "老師").start();
new Thread(threadTest1, "黃牛").start();
}
}
運行結果:
老師獲得了第10張票
小米獲得了第8張票
黃牛獲得了第9張票
小米獲得了第7張票
老師獲得了第6張票
黃牛獲得了第6張票
小米獲得了第5張票
老師獲得了第5張票
黃牛獲得了第4張票
黃牛獲得了第3張票
小米獲得了第2張票
老師獲得了第3張票
黃牛獲得了第1張票
小米獲得了第0張票
老師獲得了第0張票
案例:龜兔賽跑
背景介紹:
- 首先來個賽道距離,然後距離終點越來越近
- 判斷比賽是否結束
- 打印出勝利者
- 龜兔賽跑開始
- 故事中是烏龜贏了,兔子需要睡覺,所以我們通過sleep模擬兔子睡覺
- finally,烏龜贏得比賽
程序代碼:
public class ThreadRace 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(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判斷比賽是否結束
boolean flag = gameOver(i);
//比賽有winner,退出跑步
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"米");
}
}
//判斷比賽是否完成
private boolean gameOver(int steps){
if (winner != null) {
return true;
}else{
if (steps >= 100){
winner = Thread.currentThread().getName();
System.out.println("winner is "+ winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
ThreadRace threadRace = new ThreadRace();
new Thread(threadRace, "兔子").start();
new Thread(threadRace, "烏龜").start();
}
}
Callable接口
- 實現Callable接口,需要返回值類型
- 重寫call方法,需要拋出異常
- 創建目標對象
- 創建執行服務:
ExecutorService service = Executors.newFixedThreadPool(3);
- 提交執行:
Future<Boolean> r1 = service.submit(t1);
- 獲取結果:
boolean rs1 = r1.get();
- 關閉服務:
service.shutdown();