您好,登錄后才能下訂單哦!
關于JAVA并發編程的那些事。
記錄一下自己在學習并發編程的時候遇到的一些問題。方便自己查閱。
1.實現Runnable接口好在哪里?
從代碼架構角度:具體的任務(run方法)應該和“創建和運行線程的機制”(Thread類)相解耦。
使用繼承Thread類的方式的話,那么每次想新建一個任務,只能新建一個獨立的線程們這樣做的話損耗會比較大(比如重新創建一個線程,執行完畢
以后再銷毀等。如果實際工作內容,也就是run()函數里只是簡單的打印一行文字的話,那么可能線程的實際工作內容還不如損耗來的大)。如果使用Runnable和線程池,就可以大大減少這樣的損耗。
繼承Thread類以后,由于Java語音不支持多繼承,這樣就無法再繼承其他的類,限制了可擴展性。
2.“實現Runnable接口并傳入Thread類”和繼承Thread類,然后重寫run()方法的本質對比?
在實現多線程的本質上,并沒有區別,都是最終調用了start()方法來新建線程。這兩個方法最主要的區別是在于run()的了內容來源。
//以下代碼在Thread類中,可以找到
@override
public void run(){
if(target!=null){
target.run();
}
}
實現Runnable接口最終調用target.run()。繼承Thread類,是整個run()方法都被重寫。
3.有幾種創建線程的方法?
從不同的角度看,會有不同的答案。
典型答案是兩種,分別是實現Runnable接口和繼承Thread類
但是我們看原理,其實Thread類實現了Runnable接口,并且Thread類的run()方法,會發現其實哪兩種本質是一樣的。兩種方式在實現多線程的本質上,并沒有區別。
還有其他實現線程的方法,例如線程池等,他們也能新建線程,但是細看源碼,并沒有逃過本質,也是實現Runnable接口和繼承Thread類。
結論:我們只能那個通過新建Thread類這一種方式來出創建線程,但是類里面的run方法有兩種方式實現,的一種是重寫run()方法,第二種是實現Runnable接口的run方法,然后再把該Runnable實例傳給Thread類。除此之外,從表面上看線程池、定時器等工具類也能創建線程,但是他們的本質也是逃不過剛說的范圍。
4.start方法的執行流程是什么?
檢查線程狀態,只有New狀態的線程才能繼續,否則會拋出IllegalThreadStateException。運行或已經結束的線程都不能再啟動。
(這里可引申面試題:一個線程調用兩次start()方法會出現什么情況?why?解題思路就是1異常2線程狀態)
被加入線程組
調用start0()方法啟動線程。
Tips:start方法是被synchronized修飾的方法,可以保證線程安全。并且由JVM產檢的main方法和system組線程,并不會通過start來啟動。
5.Java中如何正確停止線程?
用interrupt來請求停止線程。(僅僅是通知到被終止的線程應該停止運行了,被停止的線程自身擁有決定權。這是一個協作機制。)
想要停止線程,需要請求方,被停止方,子方法被調用方相互配合才行:
– a.作為被停止方:每次循環或者適時檢測中斷信號,并且在可能拋出interruptedException的地方處理該中斷信號;
– b.請求方:發出中斷信號。
– c.子方法調用方:要注意優先在方法拋出InterruptedException,或者在檢查到中斷信號時,再次設置中斷狀態。(Catch里會重置這個狀態,需要再次設置中斷狀態,否則就被吞了)
最后,錯誤的方法: stop、suspend方法已經被廢棄(stop容易造成臟數據)
volatile的boolean標記,無法處理長時間阻塞的情況(例如,生產者消費者模式中,就存在這樣的情況,生產者生產速度快,消費者消費速度慢,生產者隊列阻塞)
6.無法響應中斷時如何停止線程?
如果線程阻塞是由于調用了wait()、sleep()或者join()方法,你可以中斷線程,通過拋出interruptedException異常來喚醒該線程,但是對于不能響應InterruptedException的阻塞,并沒有一個通用的解決方案。但是我們可以利用特定的其他可以響應中斷的方法,比如Reentrantlock.lockInterruptibly(),比如關閉套接字使線程立即返回等方法來達到目的。答案有很多種,因為有很多原因會造成線程阻塞,所以針對不同的情況,喚起的方法不同。總結來說,如果不支持響應中斷,就要用特定的方法來喚起。根據不同的類,調用不同的方法。
.可以響應中斷而拋出InterruptedException的常見方法?
Object.wait()/wait(long)/wait(long,int)
Thread.sleep()/sleep(long,int)
Thread.join()/join(long)/join(long,int)
java.util.concurrent.BlockingQueue.take()/put(E)
java.util.concurrent.locks.Lock.lockInterruptibly()
java.util.concurrent.countDownLatch.await()
java.util.concurrent.cyclicBarrier.await()
java.uti.concurrent.Exchanger.exchange(V)
java.nio.channels.InterruptibleChannel相關方法
java.nio.channels.Selector相關方法
7.判斷線程是否被中斷的方法有哪些?
static boolean interrupted() //返回之后,清除標記
boolean isInterrupted() //不清除
//Tip:注意Thread.intterupted()的目的對象時“當前線程”,而不管本方法來自于哪個對象
8.線程都有哪幾個狀態?
6種狀態
New:已經創建,但是還沒有執行start方法
Runnable:一旦調用了start方法后,就一定會到Runnable,java中的Runnable對應操作系統里的ready和running狀態
Blocked:當一個線程進入同步代碼塊(被Synchronized修飾),該鎖被其他線程拿走了。線程變成Blocked。只有Synchronized才能rag線程進入這個狀態。
Wating:等待
Time_waiting:計時等待
Terminated:死亡
9.線程相關方法
Thread類:
sleep相關、
join() :等待其他線程執行完畢
yield相關 :放棄已經獲得的CPU資源
currentThread:獲取當前線程的引用
start,run方法:啟動線程相關
interrupt相關::中斷線程
stop() suspend() resuem()相關 :已經廢棄
Object類:
wait():讓線程短暫休息
notify/notifyAll 相關 :喚醒線程
10.wait/notify/notifyAll的作用和用法?
階段 方法和作用
阻塞階段 調用wait()方法
喚醒階段 1、另一個線程調用這個對象的notify方法且剛好被喚醒的是本線程。2、另外一個線程調用這個對象的notifyAll方法。3、過了wait(long timeOut)的規定的超時時間,如果傳入0就是永久等待。4、線程自身調用了intterrupt()
遇到中斷 wait階段遇到中斷會拋出異常,并且釋放掉鎖
11.wait、notify、notifyAll特點?性質?
用必須先擁有monitor鎖。(Synchronized)
notify只能喚醒一個線程。
屬于Object類,是所有對象的父類,所以任何對象都能調用,并且都是native final的。
類似Condition的功能
同時持有多個鎖的情況。釋放鎖,只能釋放現在wait所對應的對象的那把鎖。
12.用wait/notify方法實現消費者生產者模式?
package com.yue.consumer;
import java.util.Date;
import java.util.LinkedList;
//使用wait notify實現一個生產者消費者模式
public class ProducerConsumerModel {
public static void main(String[] args) {
EventStorage storage = new EventStorage();
Producer producer = new Producer(storage);
Consumer consumer =new Consumer(storage);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable{
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
public void run() {
for (int i= 0;i<100;i++){
storage.put();
}
}
}
class Consumer implements Runnable{
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
public void run() {
for (int i=0;i<100;i++){
storage.take();
}
}
}
class EventStorage{
private int maxSize;
private LinkedList storage;
public EventStorage() {
this.maxSize = 10;
this.storage = new LinkedList();
}
public synchronized void put(){
while(storage.size() == maxSize){
try {
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("倉庫里有了"+storage.size()+"個產品");
notify();
}
public synchronized void take(){
while(storage.size()==0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}鄭州婦科在線醫生 http://www.zzkdfk120.com/
System.out.println("拿到了"+storage.poll()+",現在倉庫還剩下"+storage.size());
notify();
}
}
13.為什么wait需要在同步代碼塊中使用,而sleep不需要?
為了讓線程之間的通信更加可靠,防止死鎖或者永久等待。如果不放在synchronized中的話,那么久有可能在線程執行到一個wait之前,切換到另外一個線程。而另外一個線程執行完notify后,切換回來。這樣就沒有線程去喚醒它了。而sleep是針對自己線程的,和其他線程的關系不大。
14.為什么wait、notify和notifyAll定義在object類中,sleep定義在Thread類中?
因為在Java中,這三個操作都是所級別的操作,而鎖是針對對象的。鎖是綁定到對象中,而不是綁定到線程。
15.wait是屬于Object對象的,那調用Thread.wait()會出現什么情況?
會導致流程問題。因為在線程退出的時候,會自動執行一個notify
16.sleep方法的作用?
作用:讓線程在預期執行,其他時候不占用CPU資源
特點:Sleep方法可以讓線程進入waiting狀態,并且不占用CPU資源,但是不釋放鎖(包含synchronized和lock),直到規定時間之后在執行。休眠期內如果被中斷,會拋出異常并清除中斷狀態。
Tips:
//這兩種方式其實都是一樣的,但是第一種比較優雅
TimeUnit.SECONDS.sleep()
Thread.sleep()
17.wait和sleep方法的異同?
相同:
Wait和sleep方法都可以使線程阻塞,對應線程狀態是Waiting或Time_Waiting。
wait和sleep方法都可以響應中斷Thread.interrupt()。
不同點:
wait方法的執行必須在同步方法中進行,而sleep則不需要。
在同步方法里執行sleep方法時,不會釋放monitor鎖,但是wait方法會釋放monitor鎖。
sleep方法短暫休眠之后會主動退出阻塞,而沒有指定時間的 wait方法則需要被其他線程中斷后才能退出阻塞。
wait()和notify(),notifyAll()是Object類的方法,sleep()和yield()是Thread類的方法
TIps:Java設計的時候把對象都當成一把鎖,對象頭中都有鎖的狀態
18. join()方法的作用?
作用:因為新線程加入我們,所以得等他執行完再出發;通常是,主線程等待子線程,而不是子線程等待主線程。例如一般是main等thread1執行完。join遇到中斷時候是主線程被中斷,是主線程拋出異常;在join期間狀態是waiting
Tips:CountDownLatch或CyclicBarrier類封裝了join。建議使用封裝好的工具
源碼:
調用了thread.wait方法,而這方法會在thread執行結束后悔自動調用notify。這也是為什么不要使用這個的原因。
19.yield()方法的作用?
作用:釋放cpu時間片,線程狀態是runnable,而不是bolcked,也不是waiting。常用于并發包中。
yield 和 sleep:
sleep期間屬于被阻塞,yield不是阻塞,隨時是runable狀態。而且JVM是不保證遵循的。
20.線程都有哪些屬性?
編號(ID):每個編程都有自己的ID,用于標識不同的線程。
名稱(Name):作用是讓用戶或者程序員在開發、調試或運行過程中,更容易區分每個不同的線程,定位問題等。
是否是守護線程(isDeamon) :true代表該線程是守護線程,false代表線程是非守護線程,也就是用戶線程。
– 作用:給用戶線程提供服務。例如垃圾處理器。
– 特性:線程類型默認繼承自父線程。被誰啟動,一般都是JVM啟動的,(main)。不影響JVM退出。
– 區別:整體無區別。唯一區別在于是否影響JVM退出。
優先級(Priority):優先級這個屬性的目的是告訴線程調度器,用戶希望哪些線程相對多運行,哪些少運行。
– 10個優先級,默認5.
– 程序的設計不應該優先級
21.實際工作中,如何全局處理異常?為什么要全局處理?不處理行不行?
主線程可以啟動發現異常,子線程卻不行。比如主線程操作非常多,子線程雖然報異常,但是日志太多,不好發覺。并且在子線程發現問題后,并沒有停止執行。
子線程異常無法用傳統方法捕獲。
不能直接捕獲的后果,可能線程掛掉打印堆棧。用了全局處理之后提高健壯性,可以在發生未知異常后,重啟線程或者通知程序員等。
22.關于線程異常的兩種處理方法?
方案一:手動在每個run()方法里進行try catch (不推薦)
方案二:利用UncaughtExceptionHanler接口
– void uncaughtException (Thread t,Throwable e)
– 異常處理器的調用策略:首先會檢查父線程,一直往上找,查找是否有人能夠處理。
– 實現:
首先,自定義一個類實現Thread.UncaughtExceptionHandler。重寫內置方法uncaughtException(Thread t,Throwable e)方法,里面寫自己的邏輯。(Tips:可以通過構造方法來傳模塊名字)
然后,在需要配置的類中setDefaultUncaughtExceptionHandler(new HandlerInstance);
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。