面試官:java基礎怎麼樣?多線程一定會引發多線程安全問題嗎?


面試官:java基礎怎麼樣?多線程一定會引發多線程安全問題嗎?

java基礎對於學習安卓是很重要的,比如說線程,多線程。我們做安卓開發可能不太需要去研究高併發這些高深的問題,但是基礎的知識要掌握,特別是要理解為什麼會這樣?以及它的使用場景。本篇文章主要是結合常規面試題去講解基礎。現在來看看一些非常基礎的面試題。

  1. 實現線程有幾種方式?
  2. 如何啟動線程?執行run()和start()的區別。
  3. 什麼情況下才會發生線程安全問題?
  4. 怎麼樣解決線程安全問題?

以上問題是在網上搜的,也許還可以問得更細,比如多線程開啟時,它們是同一時間運行的嗎?再比如,是不是多線程就一定會發生線程安全問題?只要理解了多線程,無論面試官怎麼樣問,都能回答上。

多線程使用場景

應用場景有很多,比如打遊戲和售票。打遊戲時,如果對方打你,要等他打完你,你才能出招,這種事情你能忍?分分鐘會爆粗口。這個時候就得用到多線程,同時對打才刺激。還有我們平時春節多個窗口售票,開售時候上千人搶幾百張票,這也要用多線程才能實現。

實現線程的方式

實現線程的方式通常有2種

第一種方式是繼承Thread

<code>public class Thread1 extends Thread {
@Override
public void run() {
super.run();
for (int i = 0 ; i< 1000 ; i++){
Log.i("thread","i======" +i);
}
}
}/<code>

第二種方式是實現Runnable接口

<code>public class Thread2 implements Runnable{
@Override
public void run() {
for (int j = 0 ; j< 1000 ; j++){
Log.i("thread","j--------------------------" +j);
}
}
} /<code>

啟動線程

現在調用start()開啟上面兩個線程

<code>public class TestActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread1 thread1 = new Thread1();
thread1.start();
Thread thread2 = new Thread(new Thread2());
thread2.start();
}

}/<code>

通過查看Api文檔,知道start()方法是啟動線程。運行以後,看看打印的內容。

面試官:java基礎怎麼樣?多線程一定會引發多線程安全問題嗎?

考慮到圖太長,我只截取一部分,真實情況是一開始打印的全是i,直到i = 130的時候才開始打印j,j打印一會又開始打印i。同時開啟,按道理應該i和j輪流打印?結果證明兩個線程實際上並不是同一時間同時執行的。這就涉及到CPU對於時間的調度了,Thread1和Tread2就是兩個任務,以單核cpu為例,我把這個過程簡單歸結為下圖。

面試官:java基礎怎麼樣?多線程一定會引發多線程安全問題嗎?

cpu可能先分配1ms給Task1執行,再到分配2ms給Task2執行,然後再分配10ms給Task1執行,以此類推。所以cpu並不是同時處理兩個線程,而是同一時間段交替運行,但是由於處理的時候非常的快,以ms計算甚至更快,所以感覺兩個任務是同時執行的。(cpu分配時間我們預估不了,這只是我隨意取的時間)

將start()改為調用run()

<code>public class TestActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread1 thread1 = new Thread1();
thread1.run();
Thread thread2 = new Thread(new Thread2());
thread2.run();
}
}/<code>

運行後打印結果如下

面試官:java基礎怎麼樣?多線程一定會引發多線程安全問題嗎?

我只截了部分,實際情況是打印完i以後,才開始打印j,這就說明,是執行完thread1.run();以後,才開始執行thread2.run();,這只是單純的按順序執行相應run方法裡面的內容。調用run方法並不是開啟線程,是執行run裡面的內容,而start()是開啟線程。

多線程會有可能發生什麼問題

以多窗口售票為例子,假設有3個窗口售200張票,每個窗口排隊的人都有1000人。

面試官:java基礎怎麼樣?多線程一定會引發多線程安全問題嗎?

先寫一個簡易的售票系統。

<code>//火車售票系統
public class TicketSystem implements Runnable {
public static int ticketNum = 200;
@Override
public void run() {
for (int i = 1; i <= 1000; i++){//步驟1 1ms

if (ticketNum > 0){//步驟2 2ms
try {
Thread.sleep(50);//需要輸入相關信息之類,需要時間,而且只是假設,沒有這麼快可以買到票的。 步驟3 50ms
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNum --;
System.out.println("恭喜您,成功搶到票,還剩下:"+ticketNum+"張票");
}
}
}
}/<code>

上面的代碼很好理解,不多作解釋,上面看不懂的註釋可以先忽略,下面會介紹。開啟3個窗口去搶票

<code>public class TestActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TicketSystem ts = new TicketSystem();
//創建3個窗口
Thread thread1 = new Thread(ts);
Thread thread2 = new Thread(ts);
Thread thread3 = new Thread(ts);
thread1.start();
thread2.start();
thread3.start();


}}/<code>

運行後看下打印結果,截取了部分打印結果。

面試官:java基礎怎麼樣?多線程一定會引發多線程安全問題嗎?

出現了兩次都剩一張票,還有剩下負數的票的情況,這就是多線程有可能導致的併發問題。三個窗口就是三個線程,三個線程同時開啟。上面部分有提到,cpu是通過時間調度去交替執行這些任務。假設步驟1的for循環需要執行1ms,步驟2中的if條件判斷語句需要執行2ms,步驟3的購買操作需要50ms。票還剩下最後一張的時候,線程thread1分配到的時間是2ms,剛執行完if語句,這個時候ticketNum還是為1,然後切換到thread2,分配的時間是54ms,剛好執行完買票操作,這個時候ticketNum已經為0,但是當thread1再執行的時候,它之前已經進入了if語句,會把剩下的代碼執行完,ticketNum就為-1了,其它的情況也是同理。cpu分配的時間是我們不能掌控的,而三個線程同時操作的是同一數據ticketNum,這樣引發了不正常的結果。

在文章最開始打印i和j的時候,也是開啟了多線程,沒有出現問題。在多窗口售票開啟多線程,出現了問題,這兩個例子的區別在哪裡?區別在於多窗口售票,幾個線程訪問的是同一個共享數據,就是200張票,而i和j的例子,兩個線程訪問的數據是互不相關的。從這裡就知道,並不能說多線程就一定會發生線程安全問題,當多個線程操作同一共享數據的時候,才會引發線程安全問題。

解決線程安全問題

上述的多線程共享了同一數據,出現了線程安全問題。我們不妨把這個問題想成火車上的乘客上廁所的問題,這是一個有點味道的例子,哈哈。整條車廂有20個人同時想使用廁所,而廁所只有一個可以使用,大家是不是得要共享這個廁所?不可能讓20個人同時一起上廁所,所以在設計廁所的時候會加鎖,只要有一個人進去,把門鎖住,不管外面的人有多著急,也必須等裡面的人開鎖出來,下一個人才能進去。程序也是來源於生活 ,解決線程安全問題,我們可以在公共的核心部分加一把鎖。代碼如下:

<code>public class TicketSystem implements Runnable {
public static int ticketNum = 200;

@Override
public void run() {
for (int i = 1; i <= 1000; i++){//步驟1 1ms
synchronized (TicketSystem.class){
if (ticketNum > 0){//步驟2 2ms
try {
Thread.sleep(50);//需要輸入相關信息之類,需要時間,而且只是假設,沒有這麼快可以買到票的。 步驟3 50ms
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNum --;
System.out.println("恭喜您,成功搶到票,還剩下:"+ticketNum+"張票");
}}
}
}
}/<code>

再運行就沒有問題了。是不是感覺很簡單?因為java語言提供了這個解決辦法,不用我們自己實現。簡單的問題要力求做到最好,上了鎖就會影響運行效率,所以我們只給核心部分上鎖,核心部分越細越好,節省時間。

文章寫到這裡,開篇問的幾個問題也有了答案,現在來簡短的答一下。

1. 實現線程的幾種方式?

通常有兩種方式,繼承Thread,實現Runnable接口

2. 如何啟動線程?執行run()和start()的區別。

調用start()。執行run()是執行方法裡面的內容,start()才是開啟線程。

3. 什麼情況下才會發生線程安全問題?

當多個線程操作同一共享數據的時候。

4. 怎麼樣解決線程安全問題?

加鎖,給公共核心部分加鎖。

以上只是給出很簡短的答案,真正面試的時候還是要加上自己的理解。任何面試都一樣,只有理解了知識,才能正確的去回答問題,死記硬背答案是不可行的。

關於多線程就寫到這裡了。最近疫情還在持續,大家一起加油,堅持到可以脫口罩敲碼那天。

最後

最後我想說:對於程序員來說,要學習的知識內容、技術有太多太多,要想不被環境淘汰就只有不斷提升自己,從來都是我們去適應環境,而不是環境來適應我們!

面試官:java基礎怎麼樣?多線程一定會引發多線程安全問題嗎?

在這裡小編分享一份自己收錄整理上述技術體系圖相關的幾十套騰訊、頭條、阿里、美團等公司19年的面試題,把技術點整理成了視頻和PDF(實際上比預期多花了不少精力),包含知識脈絡 + 諸多細節,由於篇幅有限,這裡以圖片的形式給大家展示一部分。

還有 高級架構技術進階腦圖、Android開發面試專題資料,高級進階架構資料 幫助大家學習提升進階,也節省大家在網上搜索資料的時間來學習,也可以分享給身邊好友一起學習。

相信它會給大家帶來很多收穫:

面試官:java基礎怎麼樣?多線程一定會引發多線程安全問題嗎?

【Android進階學習視頻】、【全套Android面試秘籍PDF】、【Android開發核心知識點筆記】可以 私信我【安卓】免費獲取!

當程序員容易,當一個優秀的程序員是需要不斷學習的,從初級程序員到高級程序員,從初級架構師到資深架構師,或者走向管理,從技術經理到技術總監,每個階段都需要掌握不同的能力。早早確定自己的職業方向,才能在工作和能力提升中甩開同齡人。


分享到:


相關文章: