文章目錄
全部源碼:https://github.com/name365/JavaSE-30Day
轉載自atguigu.com視頻
第8章 多線程
基本概念:程序、進程、線程
- 程序(program)是爲完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼,靜態對象。
- 進程(process)是程序的一次執行過程,或是正在運行的一個程序。是一個動態的過程:有它自身的產生、存在和消亡的過程。——生命週期
- 如:運行中的QQ,運行中的MP3播放器程序是靜態的,進程是動態的
- 進程作爲資源分配的單位,系統在運行時會爲每個進程分配不同的內存區域
- 線程(thread),進程可進一步細化爲線程,是一個程序內部的一條執行路徑。
- 若一個進程同一時間並行執行多個線程,就是支持多線程的
- 線程作爲調度和執行的單位,每個線程擁有獨立的運行棧和程序計數器(pc),線程切換的開銷小
- 一個進程中的多個線程共享相同的內存單元/內存地址空間—》它們從同一堆中分配對象,可以訪問相同的變量和對象。這就使得線程間通信更簡便、高效。但多個線程操作共享的系統資源可能就會帶來安全的隱患。
進程與線程
- 單核CPU和多核CPU的理解
- 單核CPU,其實是一種假的多線程,因爲在一個時間單元內,也只能執行一個線程的任務。例如:雖然有多車道,但是收費站只有一個工作人員在收費,只有收了費才能通過,那麼CPU就好比收費人員。如果有某個人不想交錢,那麼收費人員可以把他“掛起”(晾着他,等他想通了,準備好了錢,再去收費)。但是因爲CPU時間單元特別短,因此感覺不出來。
- 如果是多核的話,才能更好的發揮多線程的效率。(現在的服務器都是多核的)
- 一個Java應用程序java.exe,其實至少有三個線程:main()主線程,gc()垃圾回收線程,異常處理線程。當然如果發生異常,會影響主線程。
- 並行與併發
- 並行:多個CPU同時執行多個任務。比如:多個人同時做不同的事。
- 併發:一個CPU(採用時間片)同時執行多個任務。比如:秒殺、多個人做同一件事。
使用多線程的優點
背景:以單核CPU爲例,只使用單個線程先後完成多個任務(調用多個方法),肯定比用多個線程來完成用的時間更短,爲何仍需多線程呢?
- 多線程程序的優點:
- 提高應用程序的響應。對圖形化界面更有意義,可增強用戶體驗。
- 提高計算機系統CPU的利用率
- 改善程序結構。將既長又複雜的進程分爲多個線程,獨立運行,利於理解和修改
何時需要多線程
- 程序需要同時執行兩個或多個任務。
- 程序需要實現一些需要等待的任務時,如用戶輸入、文件讀寫操作、網絡操作、搜索等。
- 需要一些後臺運行的程序時。
線程的創建和使用
線程的創建和啓動
- Java語言的JVM允許程序運行多個線程,它通過java.lang.Thread類來體現。
- Thread類的特性
- 每個線程都是通過某個特定Thread對象的run()方法來完成操作的,經常把run()方法的主體稱爲線程體
- 通過該Thread對象的start()方法來啓動這個線程,而非直接調用run()
Thread類
- Thread():創建新的Thread對象
- Thread(String threadname):創建線程並指定線程實例名
- Thread(Runnabletarget):指定創建線程的目標對象,它實現了Runnable接口中的run方法
- Thread(Runnable target, String name):創建新的Thread對象
API中創建線程的兩種方式
- JDK1.5之前創建新執行線程有兩種方法:
- 繼承Thread類的方式
- 實現Runnable接口的方式
創建多線程的方式一:繼承Thread類
/**
* 多線程的創建,方式一:繼承於Thread類
* 1.創建一個繼承於Thread類的子類
* 2.重寫Thread的run()方法 ---> 將此線程的方法聲明在run()中
* 3.創建Thread類的子對象
* 4.通過此對象調用start()
*
* 例子:遍歷100以內的所有的偶數
*
* @author subei
* @create 2020-05-07 15:28
*/
//1.創建一個繼承於Thread類的子類
class MyThread extends Thread{
//重寫Thread類的run()
@Override
public void run() {
for(int i = 1;i < 100;i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.創建Thread類的子對象
MyThread t1 = new MyThread();
//4.通過此對象調用start():①啓動當前線程 ②調用當前線程的run()
t1.start();
//如下操作仍在main線程中執行的
for(int i = 1;i < 100;i++){
if(i % 2 == 0){
System.out.println(i + "***main()***");
}
}
}
}
- mt子線程的創建和啓動過程
創建過程中的兩個問題說明
//1.創建一個繼承於Thread類的子類
class MyThread extends Thread{
//重寫Thread類的run()
@Override
public void run() {
for(int i = 1;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.創建Thread類的子對象
MyThread t1 = new MyThread();
//4.通過此對象調用start():①啓動當前線程 ②調用當前線程的run()
t1.start();
//問題1:我們不能通過直接調用run()的方式啓動線程。
// t1.run();
//問題二:再啓動一個線程,遍歷100以內的偶數。不可以還讓已經start()的線程去執行。會報IllegalThreadStateException
// t1.start();
//我們需要重現創建一個線程的對象,去start().
MyThread t2 = new MyThread();
t2.start();
//如下操作仍在main線程中執行的
for(int i = 1;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i + "***main()***");
}
}
}
}
練習1
- 寫法一
/**
* 練習:創建兩個分線程,其中一個遍歷100以內的偶數,另一個遍歷100以內的奇數
*
*
* @author subei
* @create 2020-05-07 16:34
*/
public class ThreadDemo {
public static void main(String[] args) {
MyThread m1 = new MyThread();
m1.start();
MyThread2 m2 = new MyThread2();
m2.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
- 寫法二
/**
* 練習:創建兩個分線程,其中一個遍歷100以內的偶數,另一個遍歷100以內的奇數
*
*
* @author subei
* @create 2020-05-07 16:34
*/
public class ThreadDemo {
public static void main(String[] args) {
//創建Thread類的匿名子類的方式
new Thread(){
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
}
}
Thread類的有關方法
/**
* 測試Thread類的常用方法
* 1.start():啓動當前線程,執行當前線程的run()
* 2.run():通常需要重寫Thread類中的此方法,將創建的線程要執行的操作聲明在此方法中
* 3.currentThread(): 靜態方法,返回當前代碼執行的線程
* 4.getName():獲取當前線程的名字
* 5.setName():設置當前線程的名字
* 6.yield():釋放當前CPU的執行權
* 7.join():在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,直到線程b完全執行完以後,線程a才
* 結束阻塞狀態。
* 8.stop():已過時。當執行此方法時,強制結束當前線程。
* 9.sleep(long millitime):讓當前線程“睡眠”指定時間的millitime毫秒)。在指定的millitime毫秒時間內,
* 當前線程是阻塞狀態的。
* 10.isAlive():返回boolean,判斷線程是否還活着
*
* @author subei
* @create 2020-05-07 16:51
*/
class HelloThread extends Thread{
@Override
public void run() {
for(int i = 0;i < 100; i++){
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
// if(i % 20 == 0){
// yield();
// }
}
}
public HelloThread(String name){
super(name);
}
}
public class ThreadModeTest {
public static void main(String[] args) {
HelloThread h1 = new HelloThread("Thread : 1");
// h1.setName("線程一");
h1.start();
//給主線程命名
Thread.currentThread().setName("主線程");
for(int i = 0;i < 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
if(i == 20){
try {
h1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(h1.isAlive());
}
}
線程的調度
-
調度策略
-
時間片
-
搶佔式:高優先級的線程搶佔CPU
-
-
Java的調度方法
- 同優先級線程組成先進先出隊列(先到先服務),使用時間片策略
- 對高優先級,使用優先調度的搶佔式策略
線程的優先級
/**
* - 線程的優先級等級
* - MAX_PRIORITY:10
* - MIN _PRIORITY:1
* - NORM_PRIORITY:5 --->默認優先級
* - 涉及的方法
* - getPriority() :返回線程優先值
* - setPriority(intnewPriority) :改變線程的優先級
*
* 說明:高優先級的線程要搶佔低優先級線程cpu的執行權。
* 但是隻是從概率上講,高優先級的線程高概率的情況下被執行。
* 並不意味着只有當高優先級的線程執行完以後,低優先級的線程纔會被執行。
*
*
* @author subei
* @create 2020-05-07 17:47
*/
class HelloThread extends Thread {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
// try {
// sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
if (j % 2 == 0) {
System.out.println(getName() + ":" + getPriority() + ":" + j);
}
}
}
public HelloThread(String name){
super(name);
}
}
public class ThreadModeTest {
public static void main(String[] args) {
HelloThread h2 = new HelloThread("Thread : 1");
h2.start();
//設置分線程的優先級
h2.setPriority(Thread.MAX_PRIORITY);
//給主線程命名
Thread.currentThread().setName("主線程");
Thread.currentThread().setPriority((Thread.MIN_PRIORITY));
for(int j = 0;j < 100; j++){
if(j % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + j);
}
// if(j == 20){
// try {
// h2.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}
System.out.println(h2.isAlive());
}
}
練習2
- 多窗口賣票
/**
* 例子:創建三個c窗口賣票,總票數爲100張
*
* 存在線程的安全問題,待解決。
*
* @author subei
* @create 2020-05-07 18:21
*/
class Windows extends Thread{
private static int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println(getName() + ":賣票,票號爲: " + ticket);
ticket--;
}else{
break;
}
}
}
}
public class WindowsTest {
public static void main(String[] args) {
Windows t1 = new Windows();
Windows t2 = new Windows();
Windows t3 = new Windows();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
練習3
/**
* 例子:創建三個c窗口賣票,總票數爲100張
*
* 存在線程的安全問題,待解決。
*
* @author subei
* @create 2020-05-07 18:21
*/
class Windows extends Thread{
private static int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println(getName() + ":賣票,票號爲: " + ticket);
ticket--;
}else{
break;
}
}
}
}
public class WindowsTest {
public static void main(String[] args) {
Windows t1 = new Windows();
Windows t2 = new Windows();
Windows t3 = new Windows();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
創建多線程的方式二:實現Runnable接口
/**
* 創建多線程的方式二:實現Runnable接口
* 1.創建一個實現了Runnable接口得類
* 2.實現類去實現Runnable中的抽象方法:run()
* 3.創建實現類的對象
* 4.將此對象作爲參數傳遞到Thread類的構造器中,創建Thread類的對象
* 5.通過Thread類的對象調用start()
*
*
* @author subei
* @create 2020-05-07 18:39
*/
//1.創建一個實現了Runnable接口得類
class MThread implements Runnable{
//2.實現類去實現Runnable中的抽象方法:run()
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3.創建實現類的對象
MThread m1 = new MThread();
//4.將此對象作爲參數傳遞到Thread類的構造器中,創建Thread類的對象
Thread t1 = new Thread(m1);
//5.通過Thread類的對象調用start():①啓動線程 ②調用當前線程的run() --> 調用了Runnable類型的target的run()
t1.start();
//再啓動一個線程,遍歷100以內的偶數
Thread t2 = new Thread(m1);
t2.setName("線程2");
t2.start();
}
}
繼承方式和實現方式的聯繫與區別
/**
* 比較創建線程的兩種方式。
* 開發中:優先選擇:實現Runnable接口的方式
* 原因:1. 實現的方式沒有類的單繼承性的侷限性
* 2. 實現的方式更適合來處理多個線程有共享數據的情況。
*
* 聯繫:public class Thread implements Runnable
* 相同點:兩種方式都需要重寫run(),將線程要執行的邏輯聲明在run()中。
*
* @author subei
* @create 2020-05-07 18:39
*/
補充:線程的分類
-
Java中的線程分爲兩類:一種是守護線程,一種是用戶線程。
它們在幾乎每個方面都是相同的,唯一的區別是判斷JVM何時離開。
守護線程是用來服務用戶線程的,通過在start()方法前調用**thread.setDaemon(true)**可以把一個用戶線程變成一個守護線程。
Java垃圾回收就是一個典型的守護線程。
若JVM中都是守護線程,當前JVM將退出。
形象理解:兔死狗烹,鳥盡弓藏
整個Java全棧系列都是筆者自己敲的筆記。寫作不易,如果可以,點個讚唄!✌