多線程相關面試題詳解

1.爲什麼用多線程?

有時候,系統需要處理非常多的執行時間很短的需求,如果每一個請求都開啓一個新線程的話,系統就要不斷的進行線程的創建和銷燬,有時花在創建和銷燬線程上的時間會比線程真正執行的時間還長。
而且線程數量太多時,系統不一定能受得了。


使用線程池主要爲了解決一下幾個問題:
通過重用線程池中的線程,來減少每個線程創建和銷燬的性能開銷。
對線程進行一些維護和管理,比如定時開始,週期執行,併發數控制。

2.線程池參數什麼意思?

比如去火車站買票,有10個售票窗口,但只有5個窗口對外開放,那麼對外開放的5個窗口稱爲核心線程數。
最大線程數是10個窗口。如果5個窗口都被佔用,那麼後來的人就必須在後面排隊,但後來售票廳越來越多,已經人滿爲患,就類似線程隊列已滿,這時候火車站站長下令,把剩下的5個窗口也打開,也就是目前有10個窗口同時運行。後來又來了一批人。10個窗口也處理不過來了,而且售票廳人已經滿了,這時候站長就下令封鎖入口,不允許其他人再進來,這就是線程異常處理策略,而線程存活時間指的是,允許售票員休息的最長時間,以此限制售票員偷懶的行爲。

3.講一講線程池中的ThreadPoolExecutor,每個參數是幹什麼的?


Executor是一個接口,跟線程池有關的基本都要跟它打交道。ThreadPoolExecutor的關係:
![image.png](
Executor接口很簡單,只有一個execute方法。
ExecutorService是Executor的子接口,增加了一些常用的對線程的控制方法,之後使用線程池主要也是使用這些方法。
AbstractExecutorService是一個抽象類。ThreadPoolExecutor就是繼承了這個類。


ThreadPoolExecutor的參數:

ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(corePoolSize,// 核心線程數
						maximumPoolSize, // 最大線程數
						keepAliveTime, // 閒置線程存活時間
						TimeUnit.MILLISECONDS,// 時間單位
						new LinkedBlockingDeque<Runnable>(),// 線程隊列
						Executors.defaultThreadFactory(),// 線程工廠
						new AbortPolicy()// 隊列已滿,而且當前線程數已經超過最大線程數時的異常處理策略
				);

corePoolSize:
核心線程數,默認情況下核心線程會一直存活,即使處於閒置狀態也不會受存keepAliveTime限制。除非allowCoreThreadTimeOut設置爲true。

maximumPoolSize:
線程池所能容納的最大線程數。超過這個數的線程將被阻塞。當任務隊列爲沒有設置大小的LinkedBlockingDeque時,這個值無效。

keepAliveTime:
非核心線程的閒置超時時間,超過這個時間就會被回收。

unit:
指定keepAliveTime的單位,如TimeUnit.SECONDS。當allowCoreThreadTim他Out,設置爲ture時對corePoolSize生效。

woreQueue:
線程池中的任務隊列,常有的有三種隊列:

  • SynchronousQueue
  • LinkedBlockingDeque
  • ArrayBlockingQueue


**threadFactory:**
線程工廠,提供創建新線程的功能。ThreadFactory是一個接口,只有一個方法: ```java public interface ThreadFactory { Thread newThread(Runnable r); } ``` 通過線程工廠可以對線程的一些屬性進行定製。

默認的工廠: ```java static class DefaultThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix;

DefaultThreadFactory() {
SecurityManager var1 = System.getSecurityManager();
this.group = var1 != null?var1.getThreadGroup():Thread.currentThread().getThreadGroup();
this.namePrefix = “pool-” + poolNumber.getAndIncrement() + “-thread-”;
}

public Thread newThread(Runnable var1) {
Thread var2 = new Thread(this.group, var1, this.namePrefix + this.threadNumber.getAndIncrement(), 0L);
if(var2.isDaemon()) {
var2.setDaemon(false);
}
if(var2.getPriority() != 5) {
var2.setPriority(5);
}
return var2;
}
}


<br />**RejectedExecutionHandler:**<br />RejectedExcutionHandler也是一個接口,只有一個方法:
```java
public interface RejectedExecutionHandler {
  void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
}

當線程池中的資源已經全部使用,添加新線程被拒絕時,會調用RejectedExcutionHandler的rejectExecution方法。

4.說下線程池內部使用規則?


線程池的線程規則執行跟任務隊列有很大的關係:
下面都假設任務隊列沒有大小限制:


如果線程數量<=核心線程數量,那麼直接啓動一個核心線程來執行任務,不會放入隊列中。


如果線程數量>核心線程數量,但<=最大線程數,並且任務隊列是LinkedBlockingDeque的時候,超過核心線程數量的任務會放在任務隊列中排隊。


如果線程數量>核心線程數,但<=最大線程數,並且任務隊列是SynchronousQueue隊列的時候,線程池會創建新的線程執行任務,這些任務也不會被放在任務隊列中。這些線程屬於非核心線程,在任務完成後,閒置時間達到了超時時間就會清除。


如果線程數量>核心線程數,並且>最大線程數,當任務隊列是LinkedBlockingDeque,會將超過核心線程的任務放在任務隊列中排隊。也就是當任務隊列是LinkedBlockingDeque並且沒有大小限制的時候,線程池的最大線程數設置是無效的,它的線程數最多不會超過核心線程數。

如果線程數量>核心線程數,並且>最大線程數,當任務隊列是SynchronousQueue 的時候,會因爲線程池拒絕添加任務而拋出異常。

任務隊列大小有限時:
當LinkedBlockingDeque 塞滿時,新增的任務會直接創建新線程來執行,當創建的線程數量超過最大線程數量時會拋異常。
SynchronousQueue 沒有數量限制,因爲它根本不保持這些任務,而是直接交給線程池去執行。當任務數量超過最大線程數時會直接拋出異常。

5.用過AtomicInteger嗎?怎麼用的?

AtomicInteger是Integer類型的原子操作類型,對於全局全量的數值類型操作num++,若沒有加synchronized關鍵字則是線程不安全的,num++,解析爲num=num+1,明顯,這個操作不具備原子性,多線程操作必然會出現問題。


看代碼:

public class AtomicIntegerTest1 {
    public static  int  count = 0;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10000; i++) {
            new Thread(() ->  count ++ ).start();
        }

        TimeUnit.SECONDS.sleep(3);
        System.out.println("count:" + count);
    }
}



**要是換成volatile修飾count變量呢?**
volatile修飾的變量能夠在多線程間保持可見性,能被多個線程同時讀但是又能保證只被單個線程寫,並且不會讀取到過期值(由JMM模型中的happen-before原則決定的)volatile修飾字段的寫入操作總是由於讀操作,即使多個線程同時修改volatile變量字段,總能獲取到最新的值。

但是volatile 僅僅保證變量在線程間保持可見性,卻依然不能保證非原子性操作。 ```java

/**

  • @description: volatile 僅僅保證變量在線程間保持可見性,卻依然不能保證非原子性操作。
  • @author: liushuai
  • @create: 2020-04-17 18:36
    **/

public class AtomicIntegerTest2 {
public static volatile int count =0;

public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 10000; i++) {
        new Thread(() -> count++).start();
    }

    TimeUnit.SECONDS.sleep(2);
    System.out.println("volatile count:" + count);
}

}


<br />atomicnteger常用方法:
```java
public final int getAndSet(int newValue)       //給AtomicInteger設置newValue並返回加oldValue
public final boolean compareAndSet(int expect, int update)    //如果輸入的值和期望值相等就set並返回true/false
public final int getAndIncrement()     //對AtomicInteger原子的加1並返回當前自增前的value
public final int getAndDecrement()   //對AtomicInteger原子的減1並返回自減之前的的value
public final int getAndAdd(int delta)   //對AtomicInteger原子的加上delta值並返加之前的value
public final int incrementAndGet()   //對AtomicInteger原子的加1並返回加1後的值
public final int decrementAndGet()    //對AtomicInteger原子的減1並返回減1後的值
public final int addAndGet(int delta)   //給AtomicInteger原子的加上指定的delta值並返回加後的值

6.用過ThreadLocal嗎?怎麼用的?

早在JDK1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal爲解決多線程程序的併發問題提供了一種新的思路。使用這個工具類可以很簡潔編寫出優美的多線程程序。


ThreadLocal很容易讓人望文生義,想當然的認爲是一個"本地線程"。


其實,ThreadLocal並不是一個Thread,而是Thread的局部變量,也許把它命名爲爲ThreadLocalVariable更容易讓人理解一些。


ThreadLocal爲變量在每個線程中都創建了一個副本,那個每個線程可以訪問自己內部的局部變量。


ThreadLocal是一個本地線程副本變量工具類。主要用於將私有線程和該線程存放的副本對象做一個映射,各個線程之間的變量各不干擾,在高併發的場景下,可以實現無狀態的調用,特別適用於各個線程依賴不同的變量值完成操作的場景。

class ConnectionManager {
     
    private static Connection connect = null;
    public static Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }
    public static void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}

假設有這樣一個數據庫連接管理類,這段代碼在單線程中使用是沒有任何問題的,但是如果再多線程中使用呢?
很顯然,在多線程中使用會存在線程安全問題:

  • 第一,這裏面的2個方法都沒有進行同步,很可能在openConnection方法中會多次創建connect;
  • 第二,由於connect是共享變量,那麼必然在調用connect的地方需要使用到同步來保證線程安全,因爲很可能一個線程在使用connect進行數據庫操作,而另外一個線程調用closeConnection關閉連接。


所以出於線程安全的考慮,必須將這段代碼的兩個方法進行同步處理,並且在調用connect的地方需要進行同步處理。


這樣將會大大影響程序執行效率,因爲一個線程在使用connect進行數據庫操作的時候,其它線程只有等待。


那麼大家來仔細分析一下這個問題,這地方到底需不需要將connect變量進行共享呢?事實上,是不需要的。假如每個線程中都有一個connect變量,各個線程之間對connect變量的訪問實際上是沒有依賴關係的,即一個不需要關心其它線程是否對這個connect進行了修改的。


到這裏,可能會有朋友想到,既然不需要在線程之間共享這個變量,可以直接這樣處理,在每個需要使用數據庫連接的方法中具體使用時才創建數據庫連接,然後在方法調用完畢再釋放這個連接。比如下面這樣:

class ConnectionManager {
    private  Connection connect = null;
    public Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }
     
    public void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}
 
 
class Dao{
    public void insert() {
        ConnectionManager connectionManager = new ConnectionManager();
        Connection connection = connectionManager.openConnection();
         
        //使用connection進行操作
         
        connectionManager.closeConnection();
    }

這樣處理確實也沒有任何問題,由於每次都是在方法內部創建的連接,那麼線程之間自然不存在線程安全問題。但是這樣會有一個致命的影響:導致服務器壓力非常大,並且嚴重影響程序執行性能。由於在方法中需要頻繁地開啓和關閉數據庫連接,這樣不盡嚴重影響程序執行效率,還可能導致服務器壓力巨大。


那麼這種情況下使用ThreadLocal是再適合不過的了,因爲ThreadLocal在每個線程中對該變量會創建一個副本,即每個線程內部都會有一個該變量,且在線程顳部任何地方都可以使用,線程之間互不影響,這樣一來就不存在線程安全問題,也不會影響程序執行性能。


但是要注意,雖然ThreadLocal能夠解決上面說的問題,但是由於在每個線程中都創建了副本,所以要考慮它對資源的消耗,比如內存的佔用會比不使用ThreadLocal要大。


![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9jZG4ubmxhcmsuY29tL3l1cXVlLzAvMjAyMC9wbmcvNDQwMjQ3LzE1ODc1MjIwMzEwODItMjExNGViZTgtMTg2Zi00Yjk3LThmNzctNjU4Y2QzYzcyOGU1LnBuZw?x-oss-process=image/format,png#align=left&display=inline&height=511&margin=[object Object]&name=image.png&originHeight=511&originWidth=485&size=140096&status=done&style=none&width=485)


從上面的結構圖,我們已經看見ThreadLocal的核心機制:

  • 每個Thread線程內部都有一個Map
  • Map裏面存在線程本地對象key和線程變量副本(value)


但是,Thread內部的Map是由ThreadLocal維護的,由ThreadLocal負責向map獲取和設置線程的變量值。
所以對於不同的線程,每次獲取副本值時,別的線程並不能獲取到當前線程的副本值,形成了副本的隔離,互不干擾。


ThreadLocal類提供瞭如下幾個核心方法:

public T get()
public void set(T value)
public void remove()
  • get()方法用於獲取當前線程的副本變量值。
  • set()方法用於保存當前線程的副本變量值。
  • initialValue()爲當前線程初始化副本變量值。
  • remove()方法移除當前線程的副本變量值。

7、程序、進程、線程的區別是什麼?舉個現實的例子說明。


** 程序(Program):**
是一個指令的集合。程序不能獨立運行,只有被加載到內存中,系統爲它分配資源後才能執行。


進程(Process):
如上所述,一個執行中的程序稱爲進程。

進程是系統分配資源的獨立單位,每個進程佔有特定的地址空間。

程序是進程的靜態文本描述,進程是程序在系統內順序執行的動態活動。

**線程(Thread): 是進程的"單一執的連續控制流程"
線程是cpu的調度和分配的基本單位,是比進程更小的能獨立運行的基本單位,也被稱爲輕量級的進程。


線程不能獨立存在,必須依附於某個進程。一個進程可以包括多個並行的線程,一個線程肯定屬於一個進程。Java虛擬機允許應用程序併發地執行多個線程。


舉例:如一個車間是程序,一個正在進行生產任務的車間是一個進程,車間內每個從事不同工作的工人是一個線程。





8.java中通過哪些方式創建多個線程類?分別使用代碼說明。並調用

  • 繼承Thread類創建線程。
/**
 * @program: java-learning->ThreadCreateTest
 * @description:
 *
 * Thread類本質上實現了Runnable接口的一個實例,代表線程的一個實例。
 * 啓動線程的唯一方法就是通過Thread類的start()實例方法
 * 這種方式實現多線程很簡單,通過自己的類直接 extends Thread;
 * 並複寫run()方法,就可以啓動新線程並執行自定定義的run()方法。
 * start
 * @author: liushuai
 * @create: 2020-04-21 09:51
 **/

public class ThreadCreateTest extends Thread{
    @Override
    public void run() {
        System.out.println("ThreadCreateTest.run()");
    }

    public static void main(String[] args) {
        new ThreadCreateTest().start();
        ThreadCreateTest createTest = new ThreadCreateTest();
        createTest.start();
    }
}

  • 實現Runnable接口創建線程
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @program: java-learning->ThreadPoolTest
 * @description:
 * @author: liushuai
 * @create: 2020-04-21 13:33
 **/

public class ThreadPoolTest {
    /** 線程池數量 10個 */
    private static int POOL_NUM = 10;

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < POOL_NUM; i++) {
            RunnableThread thread = new RunnableThread();
            executorService.execute(thread);
        }
        executorService.shutdown();
    }

}

class RunnableThread implements Runnable{
    @Override
    public void run() {
        System.out.println("通過線程池方式創建的線程:" + Thread.currentThread().getName());
    }
}

  • 實現Callable接口通過FutureTask包裝器來創建Thread線程。
public class CallableInstance<Object> implements Callable<Object> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    @Override
    public Object call() throws Exception {

        System.out.println(Thread.currentThread().getName()+"----> 我是通過實現Callable接口通過FutureTask包裝器來實現的實現");
        Object object = (Object) "success";
        return object;
    }
}


/**
 * @program: java-learning->ThreadCreateTest2
 * @description:
 *
 * 通過Callable和FutureTask創建線程
 *
 * 1: 創建Callable接口的實現類,並實現call方法
 * 2:創建Callable實現類的實現,使用FutureTask類包裝Callable對象,該FutureTask對象封裝了Callable對象的call方法的返回值
 * 3:使用FutureTask對象作爲Thread對象的Target創建並啓動線程
 * 4:調用FutureTask對象的get()方法來獲取子線程執行結束的返回值。
 * @author: liushuai
 * @create: 2020-04-21 09:59
 **/

public class ThreadCreateTest2 {

    public static void main(String[] args) throws Exception {

        Callable<Object> callable = new CallableInstance<>();
        FutureTask<Object> task = new FutureTask<>(callable);
        Thread thread = new Thread(task);
        System.out.println(Thread.currentThread().getName());
        thread.start();

    }




9.Thead類有沒有實現Runnable接口?

有實現

public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

10.當調用一個線程對象的start方法後,線程馬上進入運行狀態碼?


不是,只是進行就緒(可運行)狀態,等待分配CPU時間片。一旦得到CPU時間片,即進入運行狀態。




11. 下面的代碼,實際上有幾個線程在運行?

public static void main(String[] argc) throws Exception {
		Runnable r = new Thread6();
		Thread t = new Thread(r, "Name test");
		t.start();
}

兩個:線程t和main()方法(主線程)。


12.線程的幾種狀態

線程通常有五種狀態:

  • 新建(NEW)
    • 新創建了一個線程對象
  • 就緒(Runnable)
    • 線程對象創建後,其它線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
  • 運行(Running)
    • 就緒狀態的線程獲取了CPU,執行程序代碼
  • 阻塞
    • 阻塞狀態是線程因爲某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。
  • 死亡
    • 線程執行玩了或者因爲異常退出了run()方法,該線程結束生命


阻塞的情況又分爲三種:
  • 等待阻塞:運行的線程執行wait()方法,該線程會釋放佔用的所有資源,JVM會把該線程放入"等待池"中。

進入這個狀態後,是不能自動喚醒的,必須依靠其它線程調用notify()或notifyAll()方法才能喚醒。
wait和notify、notifyAll()是Object類的方法。

  • 同步阻塞:運行的線程在獲取對象同步鎖時,若該同步鎖被別的線程佔用,則JVM會把線程放入"鎖池"中。
  • 其他阻塞:運行的線程執行sleep()或join()方法,或者發出I/O請求時,JVM會把該線程置爲阻塞狀態。當Sleep()狀態超時、Join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。


sleep是Thread類的方法。

**

13.說說: sleep、yield、join、wait方法的區別?


sleep()方法需要指定等待的時間,它可以讓當前的線程正在執行的線程在指定的時間內暫停執行,進入阻塞狀態,該方法既可以讓其它同優先級或者高優先級的線程得到執行的機會,也可以讓低優先級的線程得到執行機會。但是sleep()方法不會釋放鎖"鎖標誌",也就是說如果有sychronized同步塊,其它線程仍然不能訪問共享數據。 作用域線程

  • Thread.sleep()方法用於來暫停線程的執行,將CPU放給線程調度器。
  • Thread.sleep()方法是一個靜態方法,它暫停的是當前執行的線程。
  • Java有兩種sleep方法,一個只有一個毫秒參數,另一個有毫秒參數,另一個有毫秒和納秒兩個參數。
  • 與wait方法不同,sleep方法不會釋放鎖。
  • 如果其它的線程中斷了一個休眠的線程,sleep方法會拋出Interrupted Exception
  • 休眠的線程在喚醒之後不保證能獲取到cpu,它會先進入就緒狀態,與其它線程競爭cpu。
  • 有一個易錯的地方,當調用t.sleep()的時候,會暫停線程t。這是不對的,因爲Thread.sleep是一個靜態方法,它會使當前線程等待而不是線程t進入休眠狀態。



join()方法
當前線程等待,調用此方法的線程執行結束在繼續執行。如:在main方法中調用t.join(),那main()方法在此時進入阻塞狀態,一直等待t線程執行完,main方法再恢復到就緒狀態,準備繼續執行。

join方法必須在線程start方法調用之後調用纔有意義。這個也很容易理解;如果一個線程都沒有start,那就它也就無法同步了。作用域線程
**
實現原理:

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }


**yield():**它僅僅釋放線程所佔有的CPU的資源,從而讓其它線程有機會運行,但是並不能保證某個特定的線程能夠獲得CPU資源。誰能獲取CPU完全取決於調度器,在有些情況下調用yield()方法的線程甚至會再次得到CPU資源。所以,依賴於yield方法是不可靠的,它只能盡力而爲。作用域線程


wait():

  • wait只能在同步(sychronzied)環境中被調用,而sleep不需要
  • 進入wait狀態的線程能夠被notify和notifyAll線程喚醒,但是進入sleeping狀態的線程不能被notify方法喚醒。
  • wait通常有條件地執行,線程會一直處於wait狀態,直到某個條件變爲真,但是sleep僅僅讓你的線程進入睡眠狀態。
  • wait方法在進入wait狀態的時候會釋放對象的鎖,但是sleep方法不會。


wait方法是針對一個被同步代碼塊加鎖的對象。
![image.png](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9jZG4ubmxhcmsuY29tL3l1cXVlLzAvMjAyMC9wbmcvNDQwMjQ3LzE1ODc1MzMzNDAzNDEtZDIzZTZhZmYtOTQ2Yi00N2JkLWEzNWUtYzI2NTY5ZTU1MWI0LnBuZw?x-oss-process=image/format,png#align=left&display=inline&height=311&margin=[object Object]&name=image.png&originHeight=311&originWidth=542&size=103214&status=done&style=none&width=542)

14.爲什麼不推薦使用stop和destroy方法來結束線程的運行?


**stop(): **此方法可以強行終止一個正在運行或掛起的線程。但stop方法不安全,就像強行切斷計算機電源,而不是正常程序關機,可能會產生不可預料的結果

舉例來說:
當一個線程對象上調用stop()方法時,這個線程對象所運行的線程就會立即停止,並拋出特殊的ThreadDeath()異常。這裏的"立即"因爲太"立即"了。
假如一個線程正在執行:

synchronized void {
 x = 3;
 y = 4;
}

由於方法時同步的,多個線程訪問時總能保證想x,y被同時賦值,而如果一個線程正在執行到x=3時,被調用了 stop()方法,即使在同步塊中,它也乾脆地stop了,這樣就產生了不完整的殘廢數據。而多線程編程中最最基礎的條件要保證數據的完整性,所以請忘記 線程的stop方法,以後我們再也不要說“停止線程”了。


destroy():該方法最初用於破壞該線程,但不作任何資源釋放。它所保持的任何監視器都會保持鎖定狀態。不過,該方法決不會被實現。即使要實現,它也極有可能以 suspend() 方式被死鎖。如果目標線程被破壞時保持一個保護關鍵系統資源的鎖,則任何線程在任何時候都無法再次訪問該資源。如果另一個線程曾試圖鎖定該資源,則會出現死鎖。

15.寫個代碼說明,終止線程的典型方式。

    1. 當run()方法執行完後,線程就自動終止了。
    1. 但有些時候run()方法不會結束(如服務器端監聽程序),或者其它需要用循環來處理的任務。在這種情況下,一般是將這些任務放在一個循環中,如while循環。如果想讓循環永遠運行下去,可以使用while(true){……}來處理。但要想使while循環在某一特定條件下退出,最直接的方法就是設一個boolean類型的標誌,並通過設置這個標誌爲true或false來控制while循環是否退出。


見代碼:

public class ThreadEndTest extends Thread{
    public volatile boolean exit = false;
    @Override
    public void run() {
        while (!exit){
            System.out.println("我還在");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadEndTest endTest = new ThreadEndTest();
        endTest.start();
        //主線程延遲5秒
        sleep(5000);
        endTest.exit=true;
        endTest.join();
        System.out.println("線程退出");
    }
}

16.A線程的優先級是10,B線程的優先級是1,那麼當進行調度時一定會調用A嗎?

不一定。線程優先級對於不同的線程調度器可能有不同的含義,可能並不是用戶直觀的推測。

17.synchronized修飾在方法前是什麼意思?

一次只能有一個線程進入該方法,其他線程要想在此時調用該方法,只能排隊等候,當前線程(就是在synchronized方法內部的線程)執行完該方法後,別的線程才能進入.

public class SynchronizedTest {

    public static synchronized void read(String name){
        System.out.println(name + "開始執行Read 方法");

        try {
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }

        System.out.println(name + "結束執行Read 方法");
    }

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println("線程:"+Thread.currentThread().getName()+"開始");
            read(Thread.currentThread().getName());
            System.out.println("線程:"+Thread.currentThread().getName()+"結束");
        };

        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();

    }
}

18.wait方法被調用時,所在的線程是否會釋放所持有的鎖資源?sleep方法呢?


wait:釋放cpu,釋放鎖
sleep:釋放cpu,不釋放鎖

19.wait、notify、notifyAll是在Thread類中定義的方法嗎?作用分別是什麼?

wait(),notify(),notifyAll()
不屬於Thread類,而是屬於Object類,也就是說每個對象都有wait(),notify(),notifyAll()的功能。因爲每個對象都有鎖,鎖是每個對象的基礎,而wait(),notify(),notifyAll()都是跟鎖有關的方法。


三個方法的作用分別是:

  • wait:導致當前線程等待,進入阻塞狀態,直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法。當前線程必須擁有此對象監視器(對象鎖)。該線程釋放對此監視器的所有權並等待,直到其他線程通過調用 notify 方法,或 notifyAll 方法通知在此對象的監視器上等待的線程醒來。然後該線程將等到重新獲得對監視器的所有權後才能繼續執行.

  • notify:喚醒在此對象監視器(對象鎖)上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。直到當前線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。此方法只應由作爲此對象監視器的所有者的線程來調用.

"當前線程必須擁有此對象監視器"與"此方法只應由作爲此對象監視器的所有者的線程來調用"說明wait方法與 notify方法必須在同步塊內執行,即synchronized(obj之內).

  • notifyAll: 喚醒在此對象監視器(對象鎖)上等待的所有線程。

20.notify是喚醒所在對象wait pool中的第一個線程嗎?

不是。
調用 notify() 方法導致解除阻塞的線程是從因調用該對象的 wait() 方法而阻塞的線程中隨機選取的,我們無法預料哪一個線程將會被選擇。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章