日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

线程中这么调用类_这些线程知识总结是真的到位!java开发两年的我看的目瞪口呆

發(fā)布時(shí)間:2024/2/28 编程问答 54 豆豆
生活随笔 收集整理的這篇文章主要介紹了 线程中这么调用类_这些线程知识总结是真的到位!java开发两年的我看的目瞪口呆 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

什么是線程:程序中負(fù)責(zé)執(zhí)行的那個(gè)東東就叫做線程(執(zhí)行路線,進(jìn)程內(nèi)部的執(zhí)行序列),或著說是進(jìn)程的子任務(wù)。

Java中實(shí)現(xiàn)多線程有幾種方法

繼承Thread類;
實(shí)現(xiàn)Runnable接口;
實(shí)現(xiàn)Callable接口通過FutureTask包裝器來創(chuàng)建Thread線程;
使用ExecutorService、Callable、Future實(shí)現(xiàn)有返回結(jié)果的多線程(也就是使用了ExecutorService來管理前面的三種方式)。

如何停止一個(gè)正在運(yùn)行的線程

  • 使用退出標(biāo)志,使線程正常退出,也就是當(dāng)run方法完成后線程終止。
  • 使用stop方法強(qiáng)行終止,但是不推薦這個(gè)方法,因?yàn)閟top和suspend及resume一樣都是過期作廢的方法。
  • 使用interrupt方法中斷線程。
class MyThread extends Thread { volatile boolean stop = false; public void run() { while (!stop) { System.out.println(getName() + " is running"); try { sleep(1000); } catch (InterruptedException e) { System.out.println("week up from blcok..."); stop = true; // 在異常處理代碼中修改共享變量的狀態(tài) } } System.out.println(getName() + " is exiting..."); }}class InterruptThreadDemo3 { public static void main(String[] args) throws InterruptedException { MyThread m1 = new MyThread(); System.out.println("Starting thread..."); m1.start(); Thread.sleep(3000); System.out.println("Interrupt thread...: " + m1.getName()); m1.stop = true; // 設(shè)置共享變量為true m1.interrupt(); // 阻塞時(shí)退出阻塞狀態(tài) Thread.sleep(3000); // 主線程休眠3秒以便觀察線程m1的中斷情況 System.out.println("Stopping application..."); }}

notify()和notifyAll()有什么區(qū)別?

notify可能會(huì)導(dǎo)致死鎖,而notifyAll則不會(huì)

任何時(shí)候只有一個(gè)線程可以獲得鎖,也就是說只有一個(gè)線程可以運(yùn)行synchronized 中的代碼

使用notifyall,可以喚醒
所有處于wait狀態(tài)的線程,使其重新進(jìn)入鎖的爭(zhēng)奪隊(duì)列中,而notify只能喚醒一個(gè)。

wait() 應(yīng)配合while循環(huán)使用,不應(yīng)使用if,務(wù)必在wait()調(diào)用前后都檢查條件,如果不滿足,必須調(diào)用notify()喚醒另外的線程來處理,自己繼續(xù)wait()直至條件滿足再往下執(zhí)行。

notify() 是對(duì)notifyAll()的一個(gè)優(yōu)化,但它有很精確的應(yīng)用場(chǎng)景,并且要求正確使用。不然可能導(dǎo)致死鎖。正確的場(chǎng)景應(yīng)該是 WaitSet中等待的是相同的條件,喚醒任一個(gè)都能正確處理接下來的事項(xiàng),如果喚醒的線程無法正確處理,務(wù)必確保繼續(xù)notify()下一個(gè)線程,并且自身需要重新回到WaitSet中.

sleep()和wait() 有什么區(qū)別?

對(duì)于sleep()方法,我們首先要知道該方法是屬于Thread類中的。而wait()方法,則是屬于Object類中的。

sleep()方法導(dǎo)致了程序暫停執(zhí)行指定的時(shí)間,讓出cpu該其他線程,但是他的監(jiān)控狀態(tài)依然保持者,當(dāng)指定的時(shí)間到了又會(huì)自動(dòng)恢復(fù)運(yùn)行狀態(tài)。在調(diào)用sleep()方法的過程中,線程不會(huì)釋放對(duì)象鎖。

當(dāng)調(diào)用wait()方法的時(shí)候,線程會(huì)放棄對(duì)象鎖,進(jìn)入等待此對(duì)象的等待鎖定池,只有針對(duì)此對(duì)象調(diào)用notify()方法后本線程才進(jìn)入對(duì)象鎖定池準(zhǔn)備,獲取對(duì)象鎖進(jìn)入運(yùn)行狀態(tài)。

volatile 是什么?可以保證有序性嗎?

一旦一個(gè)共享變量(類的成員變量、類的靜態(tài)成員變量)被volatile修飾之后,那么就具備了兩層語義:

1)保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見性,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來說是立即可見的,volatile關(guān)鍵字會(huì)強(qiáng)制將修改的值立即寫入主存。

2)禁止進(jìn)行指令重排序。

volatile 不是原子性操作

什么叫保證部分有序性?

當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫操作時(shí),在其前面的操作的更改肯定全部已經(jīng)進(jìn)行,且結(jié)果已經(jīng)對(duì)后面的操作可見;在其后面的操作肯定還沒有進(jìn)行;

x = 2; //語句1y = 0; //語句2flag = true; //語句3x = 4; //語句4y = -1; //語句5

由于flag變量為volatile變量,那么在進(jìn)行指令重排序的過程的時(shí)候,不會(huì)將語句3放到語句1、語句2前面,也不會(huì)將語句3放到語句4、語句5后面。但是要注意語句1和語句2的順序、語句4和語句5的順序是不作任何保證的。

使用 Volatile 一般用于 狀態(tài)標(biāo)記量 和 單例模式的雙檢鎖

Thread 類中的start() 和 run() 方法有什么區(qū)別?

start()方法被用來啟動(dòng)新創(chuàng)建的線程,而且start()內(nèi)部調(diào)用了run()方法,這和直接調(diào)用run()方法的效果不一樣。當(dāng)你調(diào)用run()方法的時(shí)候,只會(huì)是在原來的線程中調(diào)用,沒有新的線程啟動(dòng),start()方法才會(huì)啟動(dòng)新線程。

為什么wait, notify 和 notifyAll這些方法不在thread類里面?

明顯的原因是JAVA提供的鎖是對(duì)象級(jí)的而不是線程級(jí)的,每個(gè)對(duì)象都有鎖,通過線程獲得。如果線程需要等待某些鎖那么調(diào)用對(duì)象中的wait()方法就有意義了。如果wait()方法定義在Thread類中,線程正在等待的是哪個(gè)鎖就不明顯了。簡(jiǎn)單的說,由于wait,notify和notifyAll都是鎖級(jí)別的操作,所以把他們定義在Object類中因?yàn)殒i屬于對(duì)象。

為什么wait和notify方法要在同步塊中調(diào)用?

  • 只有在調(diào)用線程擁有某個(gè)對(duì)象的獨(dú)占鎖時(shí),才能夠調(diào)用該對(duì)象的wait(),notify()和notifyAll()方法。
  • 如果你不這么做,你的代碼會(huì)拋出IllegalMonitorStateException異常。
  • 還有一個(gè)原因是為了避免wait和notify之間產(chǎn)生競(jìng)態(tài)條件。
  • wait()方法強(qiáng)制當(dāng)前線程釋放對(duì)象鎖。這意味著在調(diào)用某對(duì)象的wait()方法之前,當(dāng)前線程必須已經(jīng)獲得該對(duì)象的鎖。因此,線程必須在某個(gè)對(duì)象的同步方法或同步代碼塊中才能調(diào)用該對(duì)象的wait()方法。

    在調(diào)用對(duì)象的notify()和notifyAll()方法之前,調(diào)用線程必須已經(jīng)得到該對(duì)象的鎖。因此,必須在某個(gè)對(duì)象的同步方法或同步代碼塊中才能調(diào)用該對(duì)象的notify()或notifyAll()方法。

    調(diào)用wait()方法的原因通常是,調(diào)用線程希望某個(gè)特殊的狀態(tài)(或變量)被設(shè)置之后再繼續(xù)執(zhí)行。調(diào)用notify()或notifyAll()方法的原因通常是,調(diào)用線程希望告訴其他等待中的線程:“特殊狀態(tài)已經(jīng)被設(shè)置”。這個(gè)狀態(tài)作為線程間通信的通道,它必須是一個(gè)可變的共享狀態(tài)(或變量)。

    Java中interrupted 和 isInterruptedd方法的區(qū)別?

    interrupted() 和 isInterrupted()的主要區(qū)別是前者會(huì)將中斷狀態(tài)清除而后者不會(huì)。Java多線程的中斷機(jī)制是用內(nèi)部標(biāo)識(shí)來實(shí)現(xiàn)的,調(diào)用Thread.interrupt()來中斷一個(gè)線程就會(huì)設(shè)置中斷標(biāo)識(shí)為true。當(dāng)中斷線程調(diào)用靜態(tài)方法Thread.interrupted()來檢查中斷狀態(tài)時(shí),中斷狀態(tài)會(huì)被清零。而非靜態(tài)方法isInterrupted()用來查詢其它線程的中斷狀態(tài)且不會(huì)改變中斷狀態(tài)標(biāo)識(shí)。簡(jiǎn)單的說就是任何拋出InterruptedException異常的方法都會(huì)將中斷狀態(tài)清零。無論如何,一個(gè)線程的中斷狀態(tài)有有可能被其它線程調(diào)用中斷來改變。

    Java中synchronized 和 ReentrantLock 有什么不同?

    相似點(diǎn):

    這兩種同步方式有很多相似之處,它們都是加鎖方式同步,而且都是阻塞式的同步,也就是說當(dāng)如果一個(gè)線程獲得了對(duì)象鎖,進(jìn)入了同步塊,其他訪問該同步塊的線程都必須阻塞在同步塊外面等待,而進(jìn)行線程阻塞和喚醒的代價(jià)是比較高的.

    區(qū)別:

    這兩種方式最大區(qū)別就是對(duì)于Synchronized來說,它是java語言的關(guān)鍵字,是原生語法層面的互斥,需要jvm實(shí)現(xiàn)。而ReentrantLock它是JDK 1.5之后提供的API層面的互斥鎖,需要lock()和unlock()方法配合try/finally語句塊來完成。

    Synchronized進(jìn)過編譯,會(huì)在同步塊的前后分別形成monitorenter和monitorexit這個(gè)兩個(gè)字節(jié)碼指令。在執(zhí)行monitorenter指令時(shí),首先要嘗試獲取對(duì)象鎖。如果這個(gè)對(duì)象沒被鎖定,或者當(dāng)前線程已經(jīng)擁有了那個(gè)對(duì)象鎖,把鎖的計(jì)算器加1,相應(yīng)的,在執(zhí)行monitorexit指令時(shí)會(huì)將鎖計(jì)算器就減1,當(dāng)計(jì)算器為0時(shí),鎖就被釋放了。如果獲取對(duì)象鎖失敗,那當(dāng)前線程就要阻塞,直到對(duì)象鎖被另一個(gè)線程釋放為止。

    由于ReentrantLock是java.util.concurrent包下提供的一套互斥鎖,相比Synchronized,ReentrantLock類提供了一些高級(jí)功能,主要有以下3項(xiàng):

    1.等待可中斷,持有鎖的線程長(zhǎng)期不釋放的時(shí)候,正在等待的線程可以選擇放棄等待,這相當(dāng)于Synchronized來說可以避免出現(xiàn)死鎖的情況。

    2.公平鎖,多個(gè)線程等待同一個(gè)鎖時(shí),必須按照申請(qǐng)鎖的時(shí)間順序獲得鎖,Synchronized鎖非公平鎖,ReentrantLock默認(rèn)的構(gòu)造函數(shù)是創(chuàng)建的非公平鎖,可以通過參數(shù)true設(shè)為公平鎖,但公平鎖表現(xiàn)的性能不是很好。

    3.鎖綁定多個(gè)條件,一個(gè)ReentrantLock對(duì)象可以同時(shí)綁定對(duì)個(gè)對(duì)象。

    有三個(gè)線程T1,T2,T3,如何保證順序執(zhí)行?

    在多線程中有多種方法讓線程按特定順序執(zhí)行,你可以用線程類的join()方法在一個(gè)線程中啟動(dòng)另一個(gè)線程,另外一個(gè)線程完成該線程繼續(xù)執(zhí)行。為了確保三個(gè)線程的順序你應(yīng)該先啟動(dòng)最后一個(gè)(T3調(diào)用T2,T2調(diào)用T1),這樣T1就會(huì)先完成而T3最后完成。

    實(shí)際上先啟動(dòng)三個(gè)線程中哪一個(gè)都行,
    因?yàn)樵诿總€(gè)線程的run方法中用join方法限定了三個(gè)線程的執(zhí)行順序。

    public class JoinTest2 { // 1.現(xiàn)在有T1、T2、T3三個(gè)線程,你怎樣保證T2在T1執(zhí)行完后執(zhí)行,T3在T2執(zhí)行完后執(zhí)行 public static void main(String[] args) { final Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("t1"); } }); final Thread t2 = new Thread(new Runnable() { @Override public void run() { try { // 引用t1線程,等待t1線程執(zhí)行完 t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t2"); } }); Thread t3 = new Thread(new Runnable() { @Override public void run() { try { // 引用t2線程,等待t2線程執(zhí)行完 t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t3"); } }); t3.start();//這里三個(gè)線程的啟動(dòng)順序可以任意,大家可以試下! t2.start(); t1.start(); }}

    SynchronizedMap和ConcurrentHashMap有什么區(qū)別?**

    SynchronizedMap()和Hashtable一樣,實(shí)現(xiàn)上在調(diào)用map所有方法時(shí),都對(duì)整個(gè)map進(jìn)行同步。而ConcurrentHashMap的實(shí)現(xiàn)卻更加精細(xì),它對(duì)map中的所有桶加了鎖。所以,只要有一個(gè)線程訪問map,其他線程就無法進(jìn)入map,而如果一個(gè)線程在訪問ConcurrentHashMap某個(gè)同時(shí),其他線程,仍然可以對(duì)map執(zhí)行某些操作。

    所以,ConcurrentHashMap在性能以及安全性方面,明顯比Collections.synchronizedMap()更加有優(yōu)勢(shì)。同時(shí),同步操作精確控制到桶,這樣,即使在遍歷map時(shí),如果其他線程試圖對(duì)map進(jìn)行數(shù)據(jù)修改,也不會(huì)拋出ConcurrentModificationException。

    什么是線程安全

    線程安全就是說多線程訪問同一代碼,不會(huì)產(chǎn)生不確定的結(jié)果。

    在多線程環(huán)境中,當(dāng)各線程不共享數(shù)據(jù)的時(shí)候,即都是私有(private)成員,那么一定是線程安全的。但這種情況并不多見,在多數(shù)情況下需要共享數(shù)據(jù),這時(shí)就需要進(jìn)行適當(dāng)?shù)耐娇刂屏恕?/p>

    線程安全一般都涉及到synchronized, 就是一段代碼同時(shí)只能有一個(gè)線程來操作 不然中間過程可能會(huì)產(chǎn)生不可預(yù)制的結(jié)果。

    如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。

    Thread類中的yield方法有什么作用?

    Yield方法可以暫停當(dāng)前正在執(zhí)行的線程對(duì)象,讓其它有相同優(yōu)先級(jí)的線程執(zhí)行。它是一個(gè)靜態(tài)方法而且只保證當(dāng)前線程放棄CPU占用而不能保證使其它線程一定能占用CPU,執(zhí)行yield()的線程有可能在進(jìn)入到暫停狀態(tài)后馬上又被執(zhí)行。

    Java線程池中submit() 和 execute()方法有什么區(qū)別?

    兩個(gè)方法都可以向線程池提交任務(wù),execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法可以返回持有計(jì)算結(jié)果的Future對(duì)象,它定義在ExecutorService接口中,它擴(kuò)展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。

    說一說自己對(duì)于 synchronized 關(guān)鍵字的了解

    synchronized關(guān)鍵字解決的是多個(gè)線程之間訪問資源的同步性,synchronized關(guān)鍵字可以保證被它修飾的方法或者代碼塊在任意時(shí)刻只能有一個(gè)線程執(zhí)行。
    另外,在 Java 早期版本中,synchronized屬于重量級(jí)鎖,效率低下,因?yàn)楸O(jiān)視器鎖(monitor)是依賴于底層的操作系統(tǒng)的 Mutex Lock 來實(shí)現(xiàn)的,Java 的線程是映射到操作系統(tǒng)的原生線程之上的。如果要掛起或者喚醒一個(gè)線程,都需要操作系統(tǒng)幫忙完成,而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換時(shí)需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài),這個(gè)狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間,時(shí)間成本相對(duì)較高,這也是為什么早期的 synchronized 效率低的原因。慶幸的是在 Java 6 之后 Java 官方對(duì)從 JVM 層面對(duì)synchronized 較大優(yōu)化,所以現(xiàn)在的 synchronized 鎖效率也優(yōu)化得很不錯(cuò)了。JDK1.6對(duì)鎖的實(shí)現(xiàn)引入了大量的優(yōu)化,如自旋鎖、適應(yīng)性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級(jí)鎖等技術(shù)來減少鎖操作的開銷。

    說說自己是怎么使用 synchronized 關(guān)鍵字,在項(xiàng)目中用到了嗎synchronized關(guān)鍵字最主要的三種使用方式:

    • 修飾實(shí)例方法: 作用于當(dāng)前對(duì)象實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前對(duì)象實(shí)例的鎖
    • 修飾靜態(tài)方法: 也就是給當(dāng)前類加鎖,會(huì)作用于類的所有對(duì)象實(shí)例,因?yàn)殪o態(tài)成員不屬于任何一個(gè)實(shí)例對(duì)象,是類成員( static 表明這是該類的一個(gè)靜態(tài)資源,不管new了多少個(gè)對(duì)象,只有一份)。所以如果一個(gè)線程A調(diào)用一個(gè)實(shí)例對(duì)象的非靜態(tài) synchronized 方法,而線程B需要調(diào)用這個(gè)實(shí)例對(duì)象所屬類的靜態(tài) synchronized 方法,是允許的,不會(huì)發(fā)生互斥現(xiàn)象,因?yàn)樵L問靜態(tài) synchronized 方法占用的鎖是當(dāng)前類的鎖,而訪問非靜態(tài) synchronized 方法占用的鎖是當(dāng)前實(shí)例對(duì)象鎖。
    • 修飾代碼塊: 指定加鎖對(duì)象,對(duì)給定對(duì)象加鎖,進(jìn)入同步代碼庫前要獲得給定對(duì)象的鎖。
    • 總結(jié): synchronized 關(guān)鍵字加到 static 靜態(tài)方法和 synchronized(class)代碼塊上都是是給 Class 類上鎖。synchronized 關(guān)鍵字加到實(shí)例方法上是給對(duì)象實(shí)例上鎖。盡量不要使用 synchronized(String a) 因?yàn)镴VM中,字符串常量池具有緩存功能!

    什么是線程安全?Vector是一個(gè)線程安全類嗎?

    如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量 的值也和預(yù)期的是一樣的,就是線程安全的。一個(gè)線程安全的計(jì)數(shù)器類的同一個(gè)實(shí)例對(duì)象在被多個(gè)線程使用的情況下也不會(huì)出現(xiàn)計(jì)算失誤。很顯然你可以將集合類分 成兩組,線程安全和非線程安全的。Vector 是用同步方法來實(shí)現(xiàn)線程安全的, 而和它相似的ArrayList不是線程安全的。

    volatile關(guān)鍵字的作用?

    一旦一個(gè)共享變量(類的成員變量、類的靜態(tài)成員變量)被volatile修飾之后,那么就具備了兩層語義:

    • 保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見性,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來說是立即可見的。
    • 禁止進(jìn)行指令重排序。
    • volatile本質(zhì)是在告訴jvm當(dāng)前變量在寄存器(工作內(nèi)存)中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問該變量,其他線程被阻塞住。
    • volatile僅能使用在變量級(jí)別;synchronized則可以使用在變量、方法、和類級(jí)別的。
    • volatile僅能實(shí)現(xiàn)變量的修改可見性,并不能保證原子性;synchronized則可以保證變量的修改可見性和原子性。
    • volatile不會(huì)造成線程的阻塞;synchronized可能會(huì)造成線程的阻塞。

    volatile標(biāo)記的變量不會(huì)被編譯器優(yōu)化;synchronized標(biāo)記的變量可以被編譯器優(yōu)化。

    常用的線程池有哪些?

    • newSingleThreadExecutor:創(chuàng)建一個(gè)單線程的線程池,此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行。
    • newFixedThreadPool:創(chuàng)建固定大小的線程池,每次提交一個(gè)任務(wù)就創(chuàng)建一個(gè)線程,直到線程達(dá)到線程池的最大大小。
    • newCachedThreadPool:創(chuàng)建一個(gè)可緩存的線程池,此線程池不會(huì)對(duì)線程池大小做限制,線程池大小完全依賴于操作系統(tǒng)(或者說JVM)能夠創(chuàng)建的最大線程大小。
    • newScheduledThreadPool:創(chuàng)建一個(gè)大小無限的線程池,此線程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求。
    • newSingleThreadExecutor:創(chuàng)建一個(gè)單線程的線程池。此線程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求。

    簡(jiǎn)述一下你對(duì)線程池的理解

    (如果問到了這樣的問題,可以展開的說一下線程池如何用、線程池的好處、線程池的啟動(dòng)策略)合理利用線程池能夠帶來三個(gè)好處。

    第一:降低資源消耗。通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。

    第二:提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要等到線程創(chuàng)建就能立即執(zhí)行。

    第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。

    Java程序是如何執(zhí)行的

    我們?nèi)粘5墓ぷ髦卸际褂瞄_發(fā)工具(IntelliJ IDEA 或 Eclipse 等)可以很方便的調(diào)試程序,或者是通過打包工具把項(xiàng)目打包成 jar 包或者 war 包,放入 Tomcat 等 Web 容器中就可以正常運(yùn)行了,但你有沒有想過 Java 程序內(nèi)部是如何執(zhí)行的?其實(shí)不論是在開發(fā)工具中運(yùn)行還是在 Tomcat 中運(yùn)行,Java 程序的執(zhí)行流程基本都是相同的,它的執(zhí)行流程如下:

    • 先把 Java 代碼編譯成字節(jié)碼,也就是把 .java 類型的文件編譯成 .class 類型的文件。這個(gè)過程的大致執(zhí)行流程:Java 源代碼 -> 詞法分析器 -> 語法分析器 -> 語義分析器 -> 字符碼生成器 -> 最終生成字節(jié)碼,其中任何一個(gè)節(jié)點(diǎn)執(zhí)行失敗就會(huì)造成編譯失敗;
    • 把 class 文件放置到 Java 虛擬機(jī),這個(gè)虛擬機(jī)通常指的是 Oracle 官方自帶的 Hotspot JVM;
    • Java 虛擬機(jī)使用類加載器(Class Loader)裝載 class 文件;
    • 類加載完成之后,會(huì)進(jìn)行字節(jié)碼校驗(yàn),字節(jié)碼校驗(yàn)通過之后 JVM 解釋器會(huì)把字節(jié)碼翻譯成機(jī)器碼交由操作系統(tǒng)執(zhí)行。但不是所有代碼都是解釋執(zhí)行的,JVM 對(duì)此做了優(yōu)化,比如,以 Hotspot 虛擬機(jī)來說,它本身提供了 JIT(Just In Time)也就是我們通常所說的動(dòng)態(tài)編譯器,它能夠在運(yùn)行時(shí)將熱點(diǎn)代碼編譯為機(jī)器碼,這個(gè)時(shí)候字節(jié)碼就變成了編譯執(zhí)行。

    說一說自己對(duì)于 synchronized 關(guān)鍵字的了解

    synchronized關(guān)鍵字解決的是多個(gè)線程之間訪問資源的同步性,synchronized關(guān)鍵字可以保證被它修飾的方法或者代碼塊在任意時(shí)刻只能有一個(gè)線程執(zhí)行。

    另外,在 Java 早期版本中,synchronized屬于重量級(jí)鎖,效率低下,因?yàn)楸O(jiān)視器鎖(monitor)是依賴于底層的操作系統(tǒng)的 Mutex Lock 來實(shí)現(xiàn)的,Java 的線程是映射到操作系統(tǒng)的原生線程之上的。如果要掛起或者喚醒一個(gè)線程,都需要操作系統(tǒng)幫忙完成,而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換時(shí)需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài),這個(gè)狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間,時(shí)間成本相對(duì)較高,這也是為什么早期的 synchronized 效率低的原因。慶幸的是在 Java 6 之后 Java 官方對(duì)從 JVM 層面對(duì)synchronized 較大優(yōu)化,所以現(xiàn)在的 synchronized 鎖效率也優(yōu)化得很不錯(cuò)了。JDK1.6對(duì)鎖的實(shí)現(xiàn)引入了大量的優(yōu)化,如自旋鎖、適應(yīng)性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級(jí)鎖等技術(shù)來減少鎖操作的開銷。

    說說自己是怎么使用 synchronized 關(guān)鍵字,在項(xiàng)目中用到了嗎

    synchronized關(guān)鍵字最主要的三種使用方式:

    • 修飾實(shí)例方法,作用于當(dāng)前對(duì)象實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前對(duì)象實(shí)例的鎖
    • 修飾靜態(tài)方法,作用于當(dāng)前類對(duì)象加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類對(duì)象的鎖 。也就是給當(dāng)前類加鎖,會(huì)作用于類的所有對(duì)象實(shí)例,因?yàn)殪o態(tài)成員不屬于任何一個(gè)實(shí)例對(duì)象,是類成員( static 表明這是該類的一個(gè)靜態(tài)資源,不管new了多少個(gè)對(duì)象,只有一份,所以對(duì)該類的所有對(duì)象都加了鎖)。所以如果一個(gè)線程A調(diào)用一個(gè)實(shí)例對(duì)象的非靜態(tài) synchronized 方法,而線程B需要調(diào)用這個(gè)實(shí)例對(duì)象所屬類的靜態(tài) synchronized 方法,是允許的,不會(huì)發(fā)生互斥現(xiàn)象,因?yàn)樵L問靜態(tài) synchronized 方法占用的鎖是當(dāng)前類的鎖,而訪問非靜態(tài) synchronized 方法占用的鎖是當(dāng)前實(shí)例對(duì)象鎖。
    • 修飾代碼塊,指定加鎖對(duì)象,對(duì)給定對(duì)象加鎖,進(jìn)入同步代碼庫前要獲得給定對(duì)象的鎖。 和 synchronized 方法一樣,synchronized(this)代碼塊也是鎖定當(dāng)前對(duì)象的。synchronized 關(guān)鍵字加到 static 靜態(tài)方法和 synchronized(class)代碼塊上都是是給 Class 類上鎖。這里再提一下:synchronized關(guān)鍵字加到非 static 靜態(tài)方法上是給對(duì)象實(shí)例上鎖。另外需要注意的是:盡量不要使用 synchronized(String a) 因?yàn)镴VM中,字符串常量池具有緩沖功能!

    下面我已一個(gè)常見的面試題為例講解一下 synchronized 關(guān)鍵字的具體使用。

    面試中面試官經(jīng)常會(huì)說:“單例模式了解嗎?來給我手寫一下!給我解釋一下雙重檢驗(yàn)鎖方式實(shí)現(xiàn)單利模式的原理唄!”

    雙重校驗(yàn)鎖實(shí)現(xiàn)對(duì)象單例(線程安全)

    public class Singleton { private volatile static Singleton uniqueInstance; private Singleton() { } public static Singleton getUniqueInstance() { //先判斷對(duì)象是否已經(jīng)實(shí)例過,沒有實(shí)例化過才進(jìn)入加鎖代碼 if (uniqueInstance == null) { //類對(duì)象加鎖 synchronized (Singleton.class) { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; }}

    另外,需要注意 uniqueInstance 采用 volatile 關(guān)鍵字修飾也是很有必要。

    uniqueInstance 采用 volatile 關(guān)鍵字修飾也是很有必要的, uniqueInstance = new Singleton(); 這段代碼其實(shí)是分為三步執(zhí)行:

  • 為 uniqueInstance 分配內(nèi)存空間
  • 初始化 uniqueInstance
  • 將 uniqueInstance 指向分配的內(nèi)存地址
  • 但是由于 JVM 具有指令重排的特性,執(zhí)行順序有可能變成 1->3->2。指令重排在單線程環(huán)境下不會(huì)出現(xiàn)問題,但是在多線程環(huán)境下會(huì)導(dǎo)致一個(gè)線程獲得還沒有初始化的實(shí)例。例如,線程 T1 執(zhí)行了 1 和 3,此時(shí) T2 調(diào)用 getUniqueInstance() 后發(fā)現(xiàn) uniqueInstance 不為空,因此返回 uniqueInstance,但此時(shí) uniqueInstance 還未被初始化。

    使用 volatile 可以禁止 JVM 的指令重排,保證在多線程環(huán)境下也能正常運(yùn)行。

    講一下 synchronized 關(guān)鍵字的底層原理

    synchronized 關(guān)鍵字底層原理屬于 JVM 層面。

    ① synchronized 同步語句塊的情況

    public class SynchronizedDemo { public void method() { synchronized (this) { System.out.println("synchronized 代碼塊"); } }}復(fù)制代碼

    通過 JDK 自帶的 javap 命令查看 SynchronizedDemo 類的相關(guān)字節(jié)碼信息:首先切換到類的對(duì)應(yīng)目錄執(zhí)行 javac SynchronizedDemo.java 命令生成編譯后的 .class 文件,然后執(zhí)行javap -c -s -v -l SynchronizedDemo.class。

    從上面我們可以看出:

    synchronized 同步語句塊的實(shí)現(xiàn)使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結(jié)束位置。 當(dāng)執(zhí)行 monitorenter 指令時(shí),線程試圖獲取鎖也就是獲取 monitor(monitor對(duì)象存在于每個(gè)Java對(duì)象的對(duì)象頭中,synchronized 鎖便是通過這種方式獲取鎖的,也是為什么Java中任意對(duì)象可以作為鎖的原因) 的持有權(quán).當(dāng)計(jì)數(shù)器為0則可以成功獲取,獲取后將鎖計(jì)數(shù)器設(shè)為1也就是加1。相應(yīng)的在執(zhí)行 monitorexit 指令后,將鎖計(jì)數(shù)器設(shè)為0,表明鎖被釋放。如果獲取對(duì)象鎖失敗,那當(dāng)前線程就要阻塞等待,直到鎖被另外一個(gè)線程釋放為止。

    ② synchronized 修飾方法的的情況

    public class SynchronizedDemo2 { public synchronized void method() { System.out.println("synchronized 方法"); }}

    synchronized 修飾的方法并沒有 monitorenter 指令和 monitorexit 指令,取而代之的確實(shí)是 ACC_SYNCHRONIZED 標(biāo)識(shí),該標(biāo)識(shí)指明了該方法是一個(gè)同步方法,JVM 通過該 ACC_SYNCHRONIZED 訪問標(biāo)志來辨別一個(gè)方法是否聲明為同步方法,從而執(zhí)行相應(yīng)的同步調(diào)用。

    為什么要用線程池?

    線程池提供了一種限制和管理資源(包括執(zhí)行一個(gè)任務(wù))。 每個(gè)線程池還維護(hù)一些基本統(tǒng)計(jì)信息,例如已完成任務(wù)的數(shù)量。

    這里借用《Java并發(fā)編程的藝術(shù)》提到的來說一下使用線程池的好處:

    • 降低資源消耗。 通過重復(fù)利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
    • 提高響應(yīng)速度。 當(dāng)任務(wù)到達(dá)時(shí),任務(wù)可以不需要的等到線程創(chuàng)建就能立即執(zhí)行。
    • 提高線程的可管理性。 線程是稀缺資源,如果無限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,使用線程池可以進(jìn)行統(tǒng)一的分配,調(diào)優(yōu)和監(jiān)控。

    實(shí)現(xiàn)Runnable接口和Callable接口的區(qū)別

    如果想讓線程池執(zhí)行任務(wù)的話需要實(shí)現(xiàn)的Runnable接口或Callable接口。 Runnable接口或Callable接口實(shí)現(xiàn)類都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執(zhí)行。兩者的區(qū)別在于 Runnable 接口不會(huì)返回結(jié)果但是 Callable 接口可以返回結(jié)果。

    備注: 工具類Executors可以實(shí)現(xiàn)Runnable對(duì)象和Callable對(duì)象之間的相互轉(zhuǎn)換。(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object resule))。

    執(zhí)行execute()方法和submit()方法的區(qū)別是什么呢?

    1)execute() 方法用于提交不需要返回值的任務(wù),所以無法判斷任務(wù)是否被線程池執(zhí)行成功與否;

    2)submit()方法用于提交需要返回值的任務(wù)。線程池會(huì)返回一個(gè)future類型的對(duì)象,通過這個(gè)future對(duì)象可以判斷任務(wù)是否執(zhí)行成功,并且可以通過future的get()方法來獲取返回值,get()方法會(huì)阻塞當(dāng)前線程直到任務(wù)完成,而使用 get(long timeout,TimeUnit unit)方法則會(huì)阻塞當(dāng)前線程一段時(shí)間后立即返回,這時(shí)候有可能任務(wù)沒有執(zhí)行完。

    如何創(chuàng)建線程池

    《阿里巴巴Java開發(fā)手冊(cè)》中強(qiáng)制線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)**

    Executors 返回線程池對(duì)象的弊端如下:

    FixedThreadPool 和 SingleThreadExecutor : 允許請(qǐng)求的隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能堆積大量的請(qǐng)求,從而導(dǎo)致OOM。CachedThreadPool 和 ScheduledThreadPool : 允許創(chuàng)建的線程數(shù)量為 Integer.MAX_VALUE ,可能會(huì)創(chuàng)建大量線程,從而導(dǎo)致OOM。

    方式一:通過構(gòu)造方法實(shí)現(xiàn)

    方式二:通過Executor 框架的工具類Executors來實(shí)現(xiàn) 我們可以創(chuàng)建三種類型的ThreadPoolExecutor:

    • FixedThreadPool : 該方法返回一個(gè)固定線程數(shù)量的線程池。該線程池中的線程數(shù)量始終不變。當(dāng)有一個(gè)新的任務(wù)提交時(shí),線程池中若有空閑線程,則立即執(zhí)行。若沒有,則新的任務(wù)會(huì)被暫存在一個(gè)任務(wù)隊(duì)列中,待有線程空閑時(shí),便處理在任務(wù)隊(duì)列中的任務(wù)。
    • SingleThreadExecutor: 方法返回一個(gè)只有一個(gè)線程的線程池。若多余一個(gè)任務(wù)被提交到該線程池,任務(wù)會(huì)被保存在一個(gè)任務(wù)隊(duì)列中,待線程空閑,按先入先出的順序執(zhí)行隊(duì)列中的任務(wù)。
    • CachedThreadPool: 該方法返回一個(gè)可根據(jù)實(shí)際情況調(diào)整線程數(shù)量的線程池。線程池的線程數(shù)量不確定,但若有空閑線程可以復(fù)用,則會(huì)優(yōu)先使用可復(fù)用的線程。若所有線程均在工作,又有新的任務(wù)提交,則會(huì)創(chuàng)建新的線程處理任務(wù)。所有線程在當(dāng)前任務(wù)執(zhí)行完畢后,將返回線程池進(jìn)行復(fù)用。

    對(duì)應(yīng)Executors工具類中的方法如圖所示:

    最后

    感謝你看到這里,文章有什么不足還請(qǐng)指正,覺得文章對(duì)你有幫助的話記得給我點(diǎn)個(gè)贊,每天都會(huì)分享java相關(guān)技術(shù)文章或行業(yè)資訊,歡迎大家關(guān)注和轉(zhuǎn)發(fā)文章!

    總結(jié)

    以上是生活随笔為你收集整理的线程中这么调用类_这些线程知识总结是真的到位!java开发两年的我看的目瞪口呆的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。