溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

關(guān)于Java的并發(fā)編程

發(fā)布時間:2020-07-20 16:29:59 來源:網(wǎng)絡(luò) 閱讀:141 作者:nineteens 欄目:編程語言

  關(guān)于JAVA并發(fā)編程的那些事。

  記錄一下自己在學(xué)習(xí)并發(fā)編程的時候遇到的一些問題。方便自己查閱。

  1.實(shí)現(xiàn)Runnable接口好在哪里?

  從代碼架構(gòu)角度:具體的任務(wù)(run方法)應(yīng)該和“創(chuàng)建和運(yùn)行線程的機(jī)制”(Thread類)相解耦。

  使用繼承Thread類的方式的話,那么每次想新建一個任務(wù),只能新建一個獨(dú)立的線程們這樣做的話損耗會比較大(比如重新創(chuàng)建一個線程,執(zhí)行完畢

  以后再銷毀等。如果實(shí)際工作內(nèi)容,也就是run()函數(shù)里只是簡單的打印一行文字的話,那么可能線程的實(shí)際工作內(nèi)容還不如損耗來的大)。如果使用Runnable和線程池,就可以大大減少這樣的損耗。

  繼承Thread類以后,由于Java語音不支持多繼承,這樣就無法再繼承其他的類,限制了可擴(kuò)展性。

  2.“實(shí)現(xiàn)Runnable接口并傳入Thread類”和繼承Thread類,然后重寫run()方法的本質(zhì)對比?

  在實(shí)現(xiàn)多線程的本質(zhì)上,并沒有區(qū)別,都是最終調(diào)用了start()方法來新建線程。這兩個方法最主要的區(qū)別是在于run()的了內(nèi)容來源。

  //以下代碼在Thread類中,可以找到

  @override

  public void run(){

  if(target!=null){

  target.run();

  }

  }

  實(shí)現(xiàn)Runnable接口最終調(diào)用target.run()。繼承Thread類,是整個run()方法都被重寫。

  3.有幾種創(chuàng)建線程的方法?

  從不同的角度看,會有不同的答案。

  典型答案是兩種,分別是實(shí)現(xiàn)Runnable接口和繼承Thread類

  但是我們看原理,其實(shí)Thread類實(shí)現(xiàn)了Runnable接口,并且Thread類的run()方法,會發(fā)現(xiàn)其實(shí)哪兩種本質(zhì)是一樣的。兩種方式在實(shí)現(xiàn)多線程的本質(zhì)上,并沒有區(qū)別。

  還有其他實(shí)現(xiàn)線程的方法,例如線程池等,他們也能新建線程,但是細(xì)看源碼,并沒有逃過本質(zhì),也是實(shí)現(xiàn)Runnable接口和繼承Thread類。

  結(jié)論:我們只能那個通過新建Thread類這一種方式來出創(chuàng)建線程,但是類里面的run方法有兩種方式實(shí)現(xiàn),的一種是重寫run()方法,第二種是實(shí)現(xiàn)Runnable接口的run方法,然后再把該Runnable實(shí)例傳給Thread類。除此之外,從表面上看線程池、定時器等工具類也能創(chuàng)建線程,但是他們的本質(zhì)也是逃不過剛說的范圍。

  4.start方法的執(zhí)行流程是什么?

  檢查線程狀態(tài),只有New狀態(tài)的線程才能繼續(xù),否則會拋出IllegalThreadStateException。運(yùn)行或已經(jīng)結(jié)束的線程都不能再啟動。

  (這里可引申面試題:一個線程調(diào)用兩次start()方法會出現(xiàn)什么情況?why?解題思路就是1異常2線程狀態(tài))

  被加入線程組

  調(diào)用start0()方法啟動線程。

  Tips:start方法是被synchronized修飾的方法,可以保證線程安全。并且由JVM產(chǎn)檢的main方法和system組線程,并不會通過start來啟動。

  5.Java中如何正確停止線程?

  用interrupt來請求停止線程。(僅僅是通知到被終止的線程應(yīng)該停止運(yùn)行了,被停止的線程自身擁有決定權(quán)。這是一個協(xié)作機(jī)制。)

  想要停止線程,需要請求方,被停止方,子方法被調(diào)用方相互配合才行:

  – a.作為被停止方:每次循環(huán)或者適時檢測中斷信號,并且在可能拋出interruptedException的地方處理該中斷信號;

  – b.請求方:發(fā)出中斷信號。

  – c.子方法調(diào)用方:要注意優(yōu)先在方法拋出InterruptedException,或者在檢查到中斷信號時,再次設(shè)置中斷狀態(tài)。(Catch里會重置這個狀態(tài),需要再次設(shè)置中斷狀態(tài),否則就被吞了)

  最后,錯誤的方法: stop、suspend方法已經(jīng)被廢棄(stop容易造成臟數(shù)據(jù))

  volatile的boolean標(biāo)記,無法處理長時間阻塞的情況(例如,生產(chǎn)者消費(fèi)者模式中,就存在這樣的情況,生產(chǎn)者生產(chǎn)速度快,消費(fèi)者消費(fèi)速度慢,生產(chǎn)者隊列阻塞)

  6.無法響應(yīng)中斷時如何停止線程?

  如果線程阻塞是由于調(diào)用了wait()、sleep()或者join()方法,你可以中斷線程,通過拋出interruptedException異常來喚醒該線程,但是對于不能響應(yīng)InterruptedException的阻塞,并沒有一個通用的解決方案。但是我們可以利用特定的其他可以響應(yīng)中斷的方法,比如Reentrantlock.lockInterruptibly(),比如關(guān)閉套接字使線程立即返回等方法來達(dá)到目的。答案有很多種,因?yàn)橛泻芏嘣驎斐删€程阻塞,所以針對不同的情況,喚起的方法不同??偨Y(jié)來說,如果不支持響應(yīng)中斷,就要用特定的方法來喚起。根據(jù)不同的類,調(diào)用不同的方法。

  .可以響應(yīng)中斷而拋出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相關(guān)方法

  java.nio.channels.Selector相關(guān)方法

  7.判斷線程是否被中斷的方法有哪些?

  static boolean interrupted() //返回之后,清除標(biāo)記

  boolean isInterrupted() //不清除

  //Tip:注意Thread.intterupted()的目的對象時“當(dāng)前線程”,而不管本方法來自于哪個對象

  8.線程都有哪幾個狀態(tài)?

  6種狀態(tài)

  New:已經(jīng)創(chuàng)建,但是還沒有執(zhí)行start方法

  Runnable:一旦調(diào)用了start方法后,就一定會到Runnable,java中的Runnable對應(yīng)操作系統(tǒng)里的ready和running狀態(tài)

  Blocked:當(dāng)一個線程進(jìn)入同步代碼塊(被Synchronized修飾),該鎖被其他線程拿走了。線程變成Blocked。只有Synchronized才能rag線程進(jìn)入這個狀態(tài)。

  Wating:等待

  Time_waiting:計時等待

  Terminated:死亡

  9.線程相關(guān)方法

  Thread類:

  sleep相關(guān)、

  join() :等待其他線程執(zhí)行完畢

  yield相關(guān) :放棄已經(jīng)獲得的CPU資源

  currentThread:獲取當(dāng)前線程的引用

  start,run方法:啟動線程相關(guān)

  interrupt相關(guān)::中斷線程

  stop() suspend() resuem()相關(guān) :已經(jīng)廢棄

  Object類:

  wait():讓線程短暫休息

  notify/notifyAll 相關(guān) :喚醒線程

  10.wait/notify/notifyAll的作用和用法?

  階段  方法和作用

  阻塞階段  調(diào)用wait()方法

  喚醒階段  1、另一個線程調(diào)用這個對象的notify方法且剛好被喚醒的是本線程。2、另外一個線程調(diào)用這個對象的notifyAll方法。3、過了wait(long timeOut)的規(guī)定的超時時間,如果傳入0就是永久等待。4、線程自身調(diào)用了intterrupt()

  遇到中斷  wait階段遇到中斷會拋出異常,并且釋放掉鎖

  11.wait、notify、notifyAll特點(diǎn)?性質(zhì)?

  用必須先擁有monitor鎖。(Synchronized)

  notify只能喚醒一個線程。

  屬于Object類,是所有對象的父類,所以任何對象都能調(diào)用,并且都是native final的。

  類似Condition的功能

  同時持有多個鎖的情況。釋放鎖,只能釋放現(xiàn)在wait所對應(yīng)的對象的那把鎖。

  12.用wait/notify方法實(shí)現(xiàn)消費(fèi)者生產(chǎn)者模式?

  package com.yue.consumer;

  import java.util.Date;

  import java.util.LinkedList;

  //使用wait notify實(shí)現(xiàn)一個生產(chǎn)者消費(fèi)者模式

  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()+"個產(chǎn)品");

  notify();

  }

  public synchronized void take(){

  while(storage.size()==0){

  try {

  wait();

  } catch (InterruptedException e) {

  e.printStackTrace();

  }

  }鄭州婦科在線醫(yī)生 http://www.zzkdfk120.com/

  System.out.println("拿到了"+storage.poll()+",現(xiàn)在倉庫還剩下"+storage.size());

  notify();

  }

  }

  13.為什么wait需要在同步代碼塊中使用,而sleep不需要?

  為了讓線程之間的通信更加可靠,防止死鎖或者永久等待。如果不放在synchronized中的話,那么久有可能在線程執(zhí)行到一個wait之前,切換到另外一個線程。而另外一個線程執(zhí)行完notify后,切換回來。這樣就沒有線程去喚醒它了。而sleep是針對自己線程的,和其他線程的關(guān)系不大。

  14.為什么wait、notify和notifyAll定義在object類中,sleep定義在Thread類中?

  因?yàn)樵贘ava中,這三個操作都是所級別的操作,而鎖是針對對象的。鎖是綁定到對象中,而不是綁定到線程。

  15.wait是屬于Object對象的,那調(diào)用Thread.wait()會出現(xiàn)什么情況?

  會導(dǎo)致流程問題。因?yàn)樵诰€程退出的時候,會自動執(zhí)行一個notify

  16.sleep方法的作用?

  作用:讓線程在預(yù)期執(zhí)行,其他時候不占用CPU資源

  特點(diǎn):Sleep方法可以讓線程進(jìn)入waiting狀態(tài),并且不占用CPU資源,但是不釋放鎖(包含synchronized和lock),直到規(guī)定時間之后在執(zhí)行。休眠期內(nèi)如果被中斷,會拋出異常并清除中斷狀態(tài)。

  Tips:

  //這兩種方式其實(shí)都是一樣的,但是第一種比較優(yōu)雅

  TimeUnit.SECONDS.sleep()

  Thread.sleep()

  17.wait和sleep方法的異同?

  相同:

  Wait和sleep方法都可以使線程阻塞,對應(yīng)線程狀態(tài)是Waiting或Time_Waiting。

  wait和sleep方法都可以響應(yīng)中斷Thread.interrupt()。

  不同點(diǎn):

  wait方法的執(zhí)行必須在同步方法中進(jìn)行,而sleep則不需要。

  在同步方法里執(zhí)行sleep方法時,不會釋放monitor鎖,但是wait方法會釋放monitor鎖。

  sleep方法短暫休眠之后會主動退出阻塞,而沒有指定時間的 wait方法則需要被其他線程中斷后才能退出阻塞。

  wait()和notify(),notifyAll()是Object類的方法,sleep()和yield()是Thread類的方法

  TIps:Java設(shè)計的時候把對象都當(dāng)成一把鎖,對象頭中都有鎖的狀態(tài)

  18. join()方法的作用?

  作用:因?yàn)樾戮€程加入我們,所以得等他執(zhí)行完再出發(fā);通常是,主線程等待子線程,而不是子線程等待主線程。例如一般是main等thread1執(zhí)行完。join遇到中斷時候是主線程被中斷,是主線程拋出異常;在join期間狀態(tài)是waiting

  Tips:CountDownLatch或CyclicBarrier類封裝了join。建議使用封裝好的工具

  源碼:

  調(diào)用了thread.wait方法,而這方法會在thread執(zhí)行結(jié)束后悔自動調(diào)用notify。這也是為什么不要使用這個的原因。

  19.yield()方法的作用?

  作用:釋放cpu時間片,線程狀態(tài)是runnable,而不是bolcked,也不是waiting。常用于并發(fā)包中。

  yield 和 sleep:

  sleep期間屬于被阻塞,yield不是阻塞,隨時是runable狀態(tài)。而且JVM是不保證遵循的。

  20.線程都有哪些屬性?

  編號(ID):每個編程都有自己的ID,用于標(biāo)識不同的線程。

  名稱(Name):作用是讓用戶或者程序員在開發(fā)、調(diào)試或運(yùn)行過程中,更容易區(qū)分每個不同的線程,定位問題等。

  是否是守護(hù)線程(isDeamon) :true代表該線程是守護(hù)線程,false代表線程是非守護(hù)線程,也就是用戶線程。

  – 作用:給用戶線程提供服務(wù)。例如垃圾處理器。

  – 特性:線程類型默認(rèn)繼承自父線程。被誰啟動,一般都是JVM啟動的,(main)。不影響JVM退出。

  – 區(qū)別:整體無區(qū)別。唯一區(qū)別在于是否影響JVM退出。

  優(yōu)先級(Priority):優(yōu)先級這個屬性的目的是告訴線程調(diào)度器,用戶希望哪些線程相對多運(yùn)行,哪些少運(yùn)行。

  – 10個優(yōu)先級,默認(rèn)5.

  – 程序的設(shè)計不應(yīng)該優(yōu)先級

  21.實(shí)際工作中,如何全局處理異常?為什么要全局處理?不處理行不行?

  主線程可以啟動發(fā)現(xiàn)異常,子線程卻不行。比如主線程操作非常多,子線程雖然報異常,但是日志太多,不好發(fā)覺。并且在子線程發(fā)現(xiàn)問題后,并沒有停止執(zhí)行。

  子線程異常無法用傳統(tǒng)方法捕獲。

  不能直接捕獲的后果,可能線程掛掉打印堆棧。用了全局處理之后提高健壯性,可以在發(fā)生未知異常后,重啟線程或者通知程序員等。

  22.關(guān)于線程異常的兩種處理方法?

  方案一:手動在每個run()方法里進(jìn)行try catch (不推薦)

  方案二:利用UncaughtExceptionHanler接口

  – void uncaughtException (Thread t,Throwable e)

  – 異常處理器的調(diào)用策略:首先會檢查父線程,一直往上找,查找是否有人能夠處理。

  – 實(shí)現(xiàn):

  首先,自定義一個類實(shí)現(xiàn)Thread.UncaughtExceptionHandler。重寫內(nèi)置方法uncaughtException(Thread t,Throwable e)方法,里面寫自己的邏輯。(Tips:可以通過構(gòu)造方法來傳模塊名字)

  然后,在需要配置的類中setDefaultUncaughtExceptionHandler(new HandlerInstance);


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI