一個線程兩次調用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)它是進程實體的一部分,是操作系統中最重要的記錄型數據結構。
進程的三種基本狀態:
- 就緒(Ready)狀態
- 執行狀態
- 阻塞狀態
線程
線程由來?
再引入線程,則是爲了減少程序在併發執行時所付出的時空開銷。
進程和線程的關係?
通常一個進程都擁有若干個線程,至少也有一個線程。
線程作爲調度和分派的基本單位,而進程作爲資源擁有的基本單位。
線程的屬性
- 線程中的實體基本上不擁有系統資源,能保證獨立運行。
- 獨立調度和分派的基本單位
- 併發執行
- 共享進程
線程上下文切換
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線程往往是不會退出的。