第17講 一個線程兩次調用sart()方法會出現什麼情況

一個線程兩次調用start()方法會出現什麼情況?談談線程的生命週期和狀態轉移。

java線程不允許啓動兩次。會拋出異常。多次調用start被認爲是編程錯誤

java5之後的線程生命週期:

  • 新建(new)
  • 就緒(Runnable)
  • 運行 (Running)
  • 阻塞 (blocking)
  • 等待(waiting)
  • 結束(Terminated)

各種狀態切換的場景:

1.RUNNABLE 與 BLOCKED 的狀態轉換

線程等待 synchronized 的隱式鎖。

等待的線程就會從 RUNNABLE 轉換到 BLOCKED 狀態。而當等待的線程獲得 synchronized 隱式鎖時,就又會從 BLOCKED 轉換到 RUNNABLE 狀態

2. RUNNABLE 與 WAITING 的狀態轉換

第一種場景,獲得 synchronized 隱式鎖的線程,調用無參數的 Object.wait() 方法。
第二種場景,調用無參數的 Thread.join() 方法。
當調用 A.join() 的時候,執行這條語句的線程會等待 thread A 執行完,而等待中的這個線程,其狀態會從 RUNNABLE 轉換到 WAITING。當線程 thread A 執行完,原來等待它的線程又會從 WAITING 狀態轉換到 RUNNABLE。
第三種場景,調用 LockSupport.park() 方法。

3. RUNNABLE 與 TIMED_WAITING 的狀態轉換

1.調用帶超時參數的 Thread.sleep(long millis) 方法;
2.獲得 synchronized 隱式鎖的線程,調用帶超時參數的 Object.wait(long timeout) 方法;
3.調用帶超時參數的 Thread.join(long millis) 方法;
4.調用帶超時參數的 LockSupport.parkNanos(Object blocker, long deadline) 方法;
5.調用帶超時參數的 LockSupport.parkUntil(long deadline) 方法。

4.從 NEW 到 RUNNABLE 狀態

調用線程對象的 start() 方法

5. 從 RUNNABLE 到 TERMINATED 狀態

線程 的 interrupt() 方法。

進程與線程

進程

進程由來?

爲了對併發執行的程序加以描述和控制,人們引入了“進程”的概念。

進程的定義

進程
是進程實體的運行過程,是系統進行資源分配調度的一個獨立單位

屬性

  • 進程是一個可擁有資源的獨立單位;
  • 進程同時是一個可獨立調度和分派的基本單位

系統是通過什麼來控制進程的?

系統總是通過 PCB 對進程進行控制的。

進程控制塊 PCB(Process Control Block)它是進程實體的一部分,是操作系統中最重要的記錄型數據結構。

進程的三種基本狀態:

  1. 就緒(Ready)狀態
  2. 執行狀態
  3. 阻塞狀態
    在這裏插入圖片描述

線程

線程由來?

再引入線程,則是爲了減少程序在併發執行時所付出的時空開銷。

進程和線程的關係?

通常一個進程都擁有若干個線程,至少也有一個線程。

線程作爲調度和分派的基本單位,而進程作爲資源擁有的基本單位。

線程的屬性

  • 線程中的實體基本上不擁有系統資源,能保證獨立運行。
  • 獨立調度和分派的基本單位
  • 併發執行
  • 共享進程

線程上下文切換

cpu通過時間片分配算法來循環執行任務。時間片就是cpu爲每個線程執行的時間。這個時間片執行時間很短,通常只有幾十毫秒。所以cpu不停的切換,讓我們以爲線程是同時執行的。

但是線程切換的時候,會保存線程的狀態,以便於下次切換回來的時候去加載這些狀態。所以線程從保存到加載狀態的過程,叫做一次上下文的切換。
上下文切換的弊端就是影響線程的執行速度。

線程實現

  • 內核線程:
  • 用戶線程:用戶程序中實現的線程

jdk1.2之前使用的是用戶線程,1.2後使用的是內核線程
從操作系統的角度,可以簡單認爲,線程是系統調度的最小單元,一個進程可以包含多個線程。

狀態和方法之間的對應圖:

在這裏插入圖片描述

新啓線程的方式

1.繼承Thread
2.實現Runnable()
3.實現Callable,允許有返回值

public class ThreadDemo {

    public static void main(String[] args){
        UseCall useCall = new UseCall();
        FutureTask<String> futureTask = new FutureTask<>(useCall);
        new Thread(futureTask).start();
    }
    
    /*實現Callable接口,允許有返回值*/
    private static class UseCall implements Callable<String> {

        @Override
        public String call() throws Exception {
            System.out.println("I am implements Callable");
            return "CallResult";
        }

    }
}

怎麼樣才能讓Java裏的線程安全停止工作?

  • 線程自然終止
    • 自然執行完
    • 拋出未處理異常
  • 通過API
    • stop(),resume(),suspend()已不建議使用,stop()會導致線程不會正確釋放資源,suspend()容易導致死鎖。
    • java線程是協作式,而非搶佔式
      調用一個線程的interrupt() 方法中斷一個線程,並不是強行關閉這個線程,只是跟這個線程打個招呼,將線程的中斷標誌位置爲true,線程是否中斷,由線程本身決定。
    • isInterrupted() 判定當前線程是否處於中斷狀態。
    • static方法interrupted() 判定當前線程是否處於中斷狀態,同時中斷標誌位改爲false。
    • 方法裏如果拋出InterruptedException,線程的中斷標誌位會被複位成false,如果確實是需要中斷線程,要求我們自己在catch語句塊裏再次調用interrupt()。

Thread類中斷線程

public class EndThread {
	
	private static class MyThread extends Thread{
		
		public MyThread(String name) {
			super(name);
		}
		
		@Override
		public void run() {
			String threadName = Thread.currentThread().getName();
			while(!isInterrupted()) {
				System.out.println(threadName+" is run!");
			}
			System.out.println(threadName+" interrput flag is "+isInterrupted());
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Thread endThread = new MyThread("myThread");
		endThread.start();
		Thread.sleep(20);
		endThread.interrupt();
	}

}

中斷Runnable類型的線程

public class EndRunnable {
	
	private static class myRunnable implements Runnable{
		
		@Override
		public void run() {

			String threadName = Thread.currentThread().getName();
			while(!Thread.currentThread().isInterrupted()) {
				System.out.println(threadName+" is run!");
			}
			System.out.println(threadName+" interrput flag is "
					+Thread.currentThread().isInterrupted());
		}			
	}

	public static void main(String[] args) throws InterruptedException {
		myRunnable useRunnable = new myRunnable();
		Thread endThread = new Thread(useRunnable,"myThread");
		endThread.start();
		Thread.sleep(20);
		endThread.interrupt();
	}

}

拋出InterruptedException異常的時候,要注意中斷標誌位

方法裏如果拋出InterruptedException,線程的中斷標誌位會被複位成false,如果確實是需要中斷線程,要求我們自己在catch語句塊裏再次調用interrupt()。

public class HasInterrputException {
	
	private static SimpleDateFormat formater 
		= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss_SSS");
	
	private static class UseThread extends Thread{
		
		public UseThread(String name) {
			super(name);
		}
		
		@Override
		public void run() {
			String threadName = Thread.currentThread().getName();
			while(!isInterrupted()) {
				try {
					System.out.println("UseThread:"+formater.format(new Date()));
					Thread.sleep(100);
				} catch (InterruptedException e) {
					System.out.println(threadName+" catch interrput flag is "
							+isInterrupted()+ " at "
							+(formater.format(new Date())));
					interrupt();
					e.printStackTrace();
				}
				System.out.println(threadName);				
			}
			System.out.println(threadName+" interrput flag is "
					+isInterrupted());
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Thread endThread = new UseThread("HasInterrputEx");
		endThread.start();
		System.out.println("Main:"+formater.format(new Date()));
		Thread.sleep(800);
		System.out.println("Main begin interrupt thread:"+formater.format(new Date()));
		endThread.interrupt();
	}

}

守護線程(Daemon Thread)

應用中需要一個長期駐留的服務程序,但是不希望其影響應用退出,就可以將其設置爲守護線程,如果JVM發現只有守護線程存在時,將結束進程。

總結就是守護線程與主線程共死。
注意,必須在線程啓動之前設置(在start之前
而且守護線程的finally不能保證一定執行

       Thread thread= new Thread();
       //必須在start之前設置
       thread.setDaemon(true);
       thread.start();

ThreadLocal

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

使用步驟:
創建一個與特定線程綁定的integer值:

ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();

如果我們想拿到值或者設置值,我們只需要調用get()或set()方法

threadLocalValue.set(1);
threadLocalValue.get();

ThreadLocal初始化使用withInitia()方法

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);

移除方法:

threadLocal.remove();

具體使用:


public class UseThreadLocal {

    static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 100;
        }
    };

    /**
     * 運行3個線程
     */
    public void StartThreadArray(){
        Thread[] runs = new Thread[3];
        for(int i=0;i<runs.length;i++){
            runs[i]=new Thread(new TestThread(i));
        }
        for(int i=0;i<runs.length;i++){
            runs[i].start();
        }
    }

    /**
     *類說明:測試線程,線程的工作是將ThreadLocal變量的值變化,並寫回,看看線程之間是否會互相影響
     */
    public static class TestThread implements Runnable {
        int id;
        public TestThread(int id){
            this.id = id;
        }
        public void run() {
            System.out.println(Thread.currentThread().getName()+":start");
            Integer s = threadLocal.get();//獲得變量的值
            s = s+id;
            threadLocal.set(s);
            System.out.println(Thread.currentThread().getName()+":"
                    + threadLocal.get());
            //threadLocal.remove();
        }
    }

    public static void main(String[] args){
        UseThreadLocal test = new UseThreadLocal();
        test.StartThreadArray();
    }
}


結果:

Thread-0:start
Thread-1:start
Thread-0:100  
Thread-2:start
Thread-1:101
Thread-2:102
注意:

它的實現結構,數據存儲於線程相關的ThreadLocalMap,其內部條目是弱引用。
通常弱引用都會和引用隊列配合清理機制使用,但是ThreadLocal是個例外,它並沒有這麼做。
這意味着,廢棄項目的回收依賴於顯式地觸發,否則就要等待線程結束,進而回收相應ThreadLocalMap!這就是很多OOM的來源,所以通常都會建議,應用一定要自己負
責remove,並且不要和線程池配合,因爲worker線程往往是不會退出的

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