這篇部落格總結了對執行緒核心api以及相關概念的學習,黑體字可以理解為重點,其他的都是我對它的理解
個人認為這些是學習java多執行緒的基礎,不理解熟悉這些,後面的也不可能學好滴
目錄
1.什麼是執行緒以及優點
二,多執行緒如何使用
三,執行緒安全問題,
四,synchronized執行過程敘述
五,幾個API:
六,停止執行緒,暫停執行緒
七,執行緒的優先順序
八,守護執行緒
一,首先搞清楚什麼是執行緒以及他的優點,我覺得一句話就就可以說清楚,執行緒就是一個程式的許多子任務。就比如你開啟瀏覽器之後可能即瀏覽網頁又在下載東西,你雖然你好像是同時做了兩件事情,但其實是\cpu不停的在兩個任務之間切換,只是切換的速度很快,感覺上是同時執行的,另外,執行緒是非同步滴。
理解這個概念之後,來看執行緒的優點,都說多執行緒能夠讓任務的執行速度變快,我覺得這句話不完全準確,並且也只是其中的一點。
首先多執行緒提交執行速度是建立在一個基礎上的,多個任務的時間執行時間是不等的,什麼意思呢?比如你現在有兩個任務,任務一用時10秒,任務二用時1秒,如果你順序執行這兩個任務,那麼任務一需要10秒,任務二執行了1秒,但是任務二的等待時間還有10秒,所以看作是執行了11秒,這總共就是21秒的執行時間,但是如果你使用多執行緒呢?可能是這樣子的,先執行任務一1秒,然後切換到任務二,執行任務2一秒,之後再切換回任務一,這樣子的話,很明顯任務二的等待時間縮短,執行時間也就更短了,相應的也就是我們的執行速度變快了。當然啦只是我自己的理解,如果你執行時間相等的話,其實切換來切換去並沒有提交效率,但是這時候還是要使用多執行緒,為撒子?
因為多執行緒的另一個優點就是能夠同時執行多個任務,總不可能讓瀏覽器每次只能開啟一個視窗吧,很簡單,就這兩個優點。
二,多執行緒如何使用
1.使用多執行緒,兩種辦法,很簡單,第一繼承Thread類,第二種實現Runnable介面,這兩個種其實就是java不支援多繼承,使用前者方法的話,你就不能繼承其他類了,但是實現Runnable的話還是可以的,並且你也可以實現其他的介面,更為靈活一些,本質上是沒有區別的,你開啟Thread的原始碼就會發現他也是實現了Runnable介面的,另外要注意實現Runnable介面的話,一般使用時候都是例項化一個該類,用它來初始化一個Thread類,start這個start類。如下:
MyRunnable m = new MyRunnable (); //實現了Runnable介面
Thread m1 = new Thread(m);
m1.setName("我的執行緒");
m1.start();
2.繼承之後,你需要做的就是書寫run() 方法以及他的其他可能需要的方法,啟動執行緒時使用Thread.start() ,注意使用了這個方法之後,cpu就會將這個任務加入到執行列當中去,之後根據一定規則分配資源進行排程該任務,排程該任務的意思就是執行該執行緒當中的 run() 方法。
3.這裡一定要理解一個點,相同優先順序的執行緒之間排程順序是隨機的,和程式碼順序無關,這個就不用說了,很簡單滴,不相信自己寫個程式測試下就行。
三,執行緒安全問題,
我們一直提到執行緒會不安全什麼的,這個不安全是這個意思:首先有一個大前提,無論你有多少個執行緒,這些執行緒都是對同一個物件進行操作或者是由同一個執行緒初始化的,下面這種情況會不安全:
ackage 第一章;
import javax.swing.plaf.TableHeaderUI;
class MyRunnable implements Runnable{
int count = 10;
public void run(){
count--;
System.out.println(Thread.currentThread().getName() + " " + count);
}
}
public class test1{
public static void main(String[] args){
MyRunnable m = new MyRunnable();
Thread m1 = new Thread(m, "A");
Thread m2 = new Thread(m,"B");
Thread m3 = new Thread(m,"c");
Thread m4 = new Thread(m,"D");
Thread m5 = new Thread(m,"E");
m1.start();
m2.start();
m3.start();
m4.start();
m5.start();
}
}
輸出的結果每一次都是不一樣的,我們期望的是count從10減到5並輸出每一個數值,但是事實上的話,可能B執行緒已經獲取到了count的值,這時候準備執行減一操作並輸出,但是程式跳到A執行緒,執行了count--操作,B可能本來獲取的值是10,之後準備count--,然後輸出9,但是在這兩部操作之間,count被A執行緒又給減一了,那麼B執行緒獲取到count的值時,就獲取到了A執行緒更改之後的值,8,但是他原本獲取到的值是9,。也就是說原本輸出9,現在輸出了8,這很明顯是不安全的。。。。這樣說不知道清楚不。輸出結果如下:
"C:\Program Files\Java\jdk-11.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\lib\idea_rt.jar=60024:C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\bin" -Dfile.encoding=UTF-8 -classpath C:\learn\java\practice\out\production\threadTest 第一章.test1
A 8
B 8
D 6
c 5
E 7
Process finished with exit code 0
下面這種情況是安全的:
就是將count變數寫在run() 函式內部,讓他成為區域性變數,這樣每一個執行緒雖然是由同一個物件初始化的,但是他們的count是屬於各自的,你無法在A執行緒裡更改B執行緒的count的值,這時候就是安全的,就這樣,不難理解,這裡就不放程式碼啦。
這裡為了解決我們的不安全問題,引出了synchronized概念,
四,synchronized執行過程敘述
這個關鍵字其實就是給一個物件的之中的某個方法上鎖,每當一個執行緒訪問該物件的該方法時,他會首先拿到這個鎖,然後開始執行裡面的程式碼,之後如果有另一個執行緒再來執行同一個物件的該方法,他會先嚐試拿到鎖,拿不到的話就會一直等著,直到鎖被釋放,再拿到鎖執行程式碼,這樣子就解決了安全問題,因為相當於每一個執行緒進入該方法之後,就是它自己的領地了,對count變數的操作都是以它自己為標準的,其他執行緒不能在中途更改count的值,直到該執行緒執行完畢。
五,幾個API:
1.CurrentThread() :獲取當前執行緒,當前執行緒指的是當前程式碼片段是在哪一個執行緒上被呼叫的,比如下面的程式碼,建構函式MyRunnable() 是在main 執行緒當中被呼叫的,但是run()函式卻是線上程A中呼叫的, 另外注意和this,getName() 的區別,後者是針對於當前this物件而言的Name
class MyRunnable implements Runnable{
int count = 10;
MyRunnable(){
System.out.println(Thread.currentThread().getName()); //輸出 main
}
synchronized public void run(){
count--;
System.out.println(Thread.currentThread().getName() + " " + count); //輸出 A
}
}
public class test1{
public static void main(String[] args){
MyRunnable m = new MyRunnable();
Thread m1 = new Thread(m, "A");
m1.start();
}
}
2.isAlive():當前執行緒是否處於活動狀態,簡單地說就是是否start()了並且run() 函式還沒有執行完 ,用法:Thread.CurrentThread.isAlive()
3.sleep(): 讓當前執行緒休眠一段時間,單位毫秒,注意這裡這個當前執行緒指的是正在執行的執行緒,就是當前程式碼片段處於哪一個執行緒當中,注意放在try語句中,用法:Thread.sleep()
4.getId(): 獲取當前執行緒唯一標識
六,停止執行緒,暫停執行緒
概述:停止執行緒簡單來說就是讓cpu放棄當前執行緒的操作,先去執行其他執行緒,但是如果使用不當,會有資料不同步,獨佔鎖等問題
終止執行緒的方法:
1.執行緒run函式呼叫結束,自動結束
2.呼叫 interrupt 函式
Thread.interrupt() : 這個函式並不是說立刻停止停止執行緒,讓執行緒停止操作,而是說在當前位置給執行緒加一個標識,表示該執行緒是應該停止的,但是並不會立即停止,那如果想要真正的停止該怎麼辦呢?使用 isTerrupted()和interrupted(),簡單來講,就是使用interrupt()停止執行緒新增標識之後,再使用相應的函式判斷是否存在這個停止標識,存在的話就不執行某一些程式碼了,這樣來達到停止的效果,但是這兩個函式是不同的,看下面
interrupted(): 測試當前執行緒是否中斷,如果中斷了,就將中斷標識,或者說狀態清除掉。簡單來講就是執行完這個函式,執行緒一定是處於執行狀態的
isInterrupted(): 測試執行緒是否中斷,沒有清楚中斷狀態的功能,並且注意判斷的並不是當前執行緒
理解兩點:1,當前執行緒和執行緒的區別,2.是否會清除中斷狀態,
看下面例子:
Thread.interrupt() :
class MyRunnable implements Runnable{
int count = 10;
MyRunnable(){
System.out.println(Thread.currentThread().getName());
}
public void run(){
for(int i=0;i<1000;i++)
{
System.out.println(i);
}
}
}
public class test1{
public static void main(String[] args){
MyRunnable m = new MyRunnable();
Thread m1 = new Thread(m, "A");
m1.start();
m1.interrupt(); //註釋該行,不註釋下一行,輸出true,false
//Thread.currentThread().interrupt();
System.out.println("測試是否中斷1:"+Thread.interrupted()); //輸出false
System.out.println("測試是否中斷2:"+Thread.interrupted()); //輸出false
}
}
根據執行結果false來講,我們停止了m1,執行緒,但是輸出為false,因為呼叫interrupted方法程式碼處於的執行緒是main執行緒,main並沒有停止,所以輸出false,但是如果將註釋去掉,將m1.interrupt()註釋掉,會輸出true ,false,為什麼第二是false?這就是因為該函式具有清除中斷狀態的作用,第一次執行之後main就不再是中斷狀態了,所以輸出false
isterrupted()方法:不具有清除狀態作用,檢測的是執行緒物件,一個具體的物件,不是當前執行緒,如下
package 第一章;
import javax.swing.plaf.TableHeaderUI;
class MyRunnable implements Runnable{
int count = 10;
MyRunnable(){
System.out.println(Thread.currentThread().getName());
}
public void run(){
for(int i=0;i<1000;i++)
{
System.out.println(i);
}
}
}
public class test1{
public static void main(String[] args){
MyRunnable m = new MyRunnable();
Thread m1 = new Thread(m, "A");
m1.start();
m1.interrupt();
//Thread.currentThread().interrupt();
System.out.println("測試是否中斷1:&&&&&&&&&&&&&&&&&&&&&&&"+m1.isInterrupted()); //輸出true
System.out.println("測試是否中斷2:&&&&&&&&&&&&&&&&&&&"+m1.isInterrupted()); //輸出true
}
}
都輸出true,就是因為他們呼叫的是物件m1的,m1被停止了,所以肯定輸出true,不過注意,這個true會輸出在任意位置,cpu呼叫決定的,所以你如果測試的話加點標識找起來方便。
所以,結合來講的話,停止執行緒,run方法可以修改為如下:
public void run(){
for(int i=0;i<1000;i++)
{
if(Thread.interrupted()){
System.out.println("停止啦");
break;
}
System.out.println(i);
}
//如果給這裡加上語句,不判斷是否停止,他還是會執行的
}
執行結果,可以看到輸出了一些數字之後就停止了
。。。
205 206 207 208 209 210 211 212 停止啦 Process finished with exit code 0
當然這是一種軟性的停止,你需要判斷他是否停止了,下面介紹一種硬性的停止,丟擲異常,很簡單,將run 方法改為如下:
public void run(){
try{
for(int i=0;i<1000;i++)
{
if(Thread.interrupted()){
System.out.println("停止啦");
throw new InterruptedException();
}
System.out.println(i);
}
//for迴圈外面有語句也不會執行了,捕捉到異常
}catch (InterruptedException e){
e.printStackTrace();
}
}
一種情況,在沉睡中停止執行緒,
什麼意思呢?假設你現線上程中斷了,然後你再去呼叫Thread.sleep()函式讓執行緒休眠,或者執行緒已經休眠了,你再去呼叫interrrupt()函式,那就會產生異常,並清除執行緒的中斷狀態。這兩種情況是對立矛盾的,所以會產生異常,並且因為產生了異常,所以執行緒一定是被中斷的
比如下面的程式碼,m1.start()之後,m1執行緒進行了sleep(),之後在main中又中斷了m1執行緒,這明顯是不合理的,所以會輸出異常之中的內容
class MyRunnable implements Runnable{
int count = 10;
MyRunnable(){
System.out.println(Thread.currentThread().getName());
}
public void run(){
try{
for(int i=0;i<1000;i++)
{
System.out.println(i);
Thread.sleep(1000);
}}catch (InterruptedException e){
System.out.println("先休眠再停止");
e.printStackTrace();
}
}
}
public class test1{
public static void main(String[] args){
MyRunnable m = new MyRunnable();
Thread m1 = new Thread(m, "A");
m1.start();
try { //加這個語句是為了讓m1的中斷執行在睡眠之後
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
m1.interrupt();
}
}
輸出如下:
main
0
先停止再休眠
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at 第一章.MyRunnable.run(test1.java:15)
at java.base/java.lang.Thread.run(Thread.java:834)
Process finished with exit code 0
3.使用stop
這種方法已經被廢除了,會造成很多問題
最簡單的一個問題,資料不同步,假設你去銀行,取1000,存1000,然後給你操作時候,這兩個操作時在一個方法裡面的,並且這個方法是上了鎖的。在程式執行完了取1000之後,程式去上了個廁所,休眠了1秒,休眠期間,你使用了stop函式將執行緒強行停止,釋放了物件鎖,相當於後面存1000這個操作就沒有做了,這很明顯是不合理的,資料不同步了。
4.暫停執行緒
暫停執行緒是說將當前執行緒的操作暫停,過一會再回來繼續執行,注意和中斷的區別,它並不會釋放物件鎖
suspend()暫停函式,
resume()恢復函式
這兩個函式也已經被廢除了,因為可能造成獨佔鎖和不同步的問題,廢除我們也要知道他為撒子廢除了,不想看的朋友可以跳過下面
獨佔鎖問題:
不想放太多的程式碼,放一點,然後用文字說明,更能表達思路
有這樣一個物件,比較簡潔,不想寫沒用的程式碼,意思就是碰到名字為a的執行緒,就暫停當前執行緒
class test{
synchronized public void printStr() { if(Thread.currentThread().getName().equals("a")){
System.out.println("列印。。。。。"); Thread.currentThread().suspend(); } }
}
class MyRunnable implements Runnable{
int count = 10;
MyRunnable(){
System.out.println(Thread.currentThread().getName());
}
public void run() {
//呼叫test例項的printStr方法
}
}
現在,你初始化了a,b兩個執行緒,使用了一個MyRunnable例項初始化的,然後你先start了a執行緒,a執行緒執行了printStr()方法,獲取到了test例項的物件鎖,然後被suspend了,但是它並沒有釋放test例項的物件鎖,這時候如果b執行緒開啟,他是無法執行printStr()方法的,因為它他拿不到物件鎖,只能等著,這是不合理的。而且這裡有一個有意思的問題,如果你這裡使用了suspend的話,你如果想要在其他執行緒裡面使用System.out.println() 方法,是不可以的,因為out物件的println()方法已經被鎖定了。就是說你無法拿到他的鎖,使用不了這個方法了,看下面的例子
class MyThread extends Thread{
private int i=0;
public void run(){
while(true){
System.out.println(i); //佔用了println() 方法
i++;
}
}
}
public class test1{
public static void main(String[] args){
MyThread m = new MyThread();
m.start();
try {
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
m.suspend();
System.out.println("main"); //不會列印
}
}
輸出結果就是列印到某一個數字然後停止,永遠不會列印 main ,其實你看一下println()方法原始碼就明白了:裡面的程式碼被鎖住了,a執行緒獨佔了這個鎖
public void println(int x) {
synchronized (this) {
print(x);
newLine();
}
}
不同步問題:
這個我覺得和前面stop的問題是一樣的,不說了
七,執行緒的優先順序
概述:前面我們看到的main執行緒還有自己建立的執行緒,程式碼執行都是隨機的,55開滴,但是現實中執行緒肯定是有輕重緩急的,所以就有了執行緒優先順序這一個概念,執行緒優先順序高的執行的概率會大一點,但是也不是一定的,只是相對來講大一點。
個人感覺這塊沒撒說的,就幾個概念也都很好理解,自己寫一些程式碼看看就行。
1.setPriotity(int ) 設定優先順序
2.getPriotity() 獲取優先順序
3.優先順序具有繼承性:比如你在A執行緒裡啟動了B執行緒,則B執行緒的優先順序是和A執行緒一樣的,除非你手動進行了設定。很簡單吧
4.優先順序具有隨機性:這個也很理解,優先順序高的不一定每一次都比優先順序低先執行完,因為cpu在排程的時候肯定是以概率的方式來排程執行緒的,概率嘛,什麼都有可能,只能說樣本足夠大時,優先順序高的一定先執行完。
八,守護執行緒
java裡面有兩種執行緒,使用者執行緒和守護執行緒,使用者執行緒就是我之前定義的那些,當然也包括main執行緒,守護執行緒,舉一個最簡單的例子,java的垃圾回收機制,它是在什麼時候回收的呢?你會跟隨著用於執行緒,有沒有delete的空間了,就清除掉它,直到所有執行緒都結束之後,它才會自動銷燬。簡單來說,就是一直陪伴著使用者執行緒,幫助使用者執行緒做一些事情,使用者執行緒全部結束了,它也就結束了。
設定方法:thread.setDaemon(true); 預設是false的
個人覺得可能做一些需要不停的做的事情會用到該執行緒。