Java多線(xiàn)程之ThreadLocal淺析
介紹
什么是ThreadLocal?
ThreadLocal叫做線(xiàn)程變量,用于在多線(xiàn)程環(huán)境下創(chuàng)建線(xiàn)程本地變量。
通俗的講,ThreadLocal可以讓你在同一個(gè)線(xiàn)程中創(chuàng)建一個(gè)變量,并且這個(gè)變量對(duì)于該線(xiàn)程是唯一的,其他線(xiàn)程無(wú)法訪(fǎng)問(wèn)到這個(gè)變量。
這種方式能夠有效地避免多線(xiàn)程之間的變量沖突問(wèn)題,使得線(xiàn)程本地變量的訪(fǎng)問(wèn)變得更加安全和高效。
例如,在一個(gè)線(xiàn)程池中,每個(gè)線(xiàn)程需要維護(hù)自己的狀態(tài),這時(shí)就可以使用ThreadLocal來(lái)創(chuàng)建線(xiàn)程本地變量來(lái)存儲(chǔ)狀態(tài)信息。
ThreadLocal 的作用是什么?
在多線(xiàn)程編程中,由于不同線(xiàn)程之間共享內(nèi)存,如果多個(gè)線(xiàn)程訪(fǎng)問(wèn)同一個(gè)變量,就會(huì)發(fā)生競(jìng)爭(zhēng)條件,可能會(huì)導(dǎo)致數(shù)據(jù)不一致或者死鎖等問(wèn)題。使用ThreadLocal可以解決這個(gè)問(wèn)題,因?yàn)樗梢詾槊總€(gè)線(xiàn)程創(chuàng)建一個(gè)獨(dú)立的變量副本,每個(gè)線(xiàn)程都可以訪(fǎng)問(wèn)自己的變量副本,而不會(huì)影響其他線(xiàn)程的變量。這種方式可以有效地避免多線(xiàn)程之間的變量沖突問(wèn)題,提高了程序的可靠性和性能。ThreadLocal常用于實(shí)現(xiàn)線(xiàn)程安全的單例模式,以及在多線(xiàn)程環(huán)境下對(duì)共享數(shù)據(jù)的緩存。
如何使用ThreadLocal
如何創(chuàng)建一個(gè)ThreadLocal實(shí)例
直接上代碼:
public class ThreadLocalDemo { private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { new Thread(() -> { System.out.println("thread1 before set: " + threadLocal.get()); threadLocal.set("AAAAA"); System.out.println("thread1 after set: " + threadLocal.get()); threadLocal.remove(); System.out.println("thread1 after remove: " + threadLocal.get()); }).start(); new Thread(() -> { System.out.println("thread1 before set: " + threadLocal.get()); threadLocal.set("BBBBB"); System.out.println("thread2 after set: " + threadLocal.get()); threadLocal.remove(); System.out.println("thread2 after remove: " + threadLocal.get()); }).start(); System.out.println("main thread before set: " + threadLocal.get()); threadLocal.set("Main"); System.out.println("main after set: " + threadLocal.get()); threadLocal.remove(); System.out.println("main after remove: " + threadLocal.get()); } } 程序輸出: thread1 before set: null main thread before set: null main after set: Main thread1 before set: null thread1 after set: AAAAA thread1 after remove: null thread2 after set: BBBBB thread2 after remove: null main after remove: null
創(chuàng)建ThreadLocal實(shí)例的方式非常簡(jiǎn)單,只需要使用Java中的ThreadLocal類(lèi)的構(gòu)造函數(shù)即可。
上面的代碼創(chuàng)建了一個(gè)ThreadLocal實(shí)例,該實(shí)例可以存儲(chǔ)String類(lèi)型的值。在使用ThreadLocal之前,需要先調(diào)用它的set()方法來(lái)初始化一個(gè)線(xiàn)程本地變量, 否則get()方法得到的值就是null。
從代碼中可以看到, 我們?cè)趍ain方法中分別創(chuàng)建了2個(gè)線(xiàn)程, 三個(gè)線(xiàn)程分表獲取了自己線(xiàn)程存放的變量,他們之間變量的獲取并不會(huì)錯(cuò)亂。
如果在當(dāng)前線(xiàn)程中尚未設(shè)置該值或者已經(jīng)調(diào)用remove()方法刪除值,則返回null。
需要注意的是,每個(gè)ThreadLocal對(duì)象只能存儲(chǔ)一個(gè)值,如果需要存儲(chǔ)多個(gè)值,則需要?jiǎng)?chuàng)建多個(gè)ThreadLocal對(duì)象。
ThreadLocal與Synchronized的區(qū)別
ThreadLocal和Synchronized都是Java中用于處理多線(xiàn)程并發(fā)訪(fǎng)問(wèn)的工具,但它們的作用和實(shí)現(xiàn)方式有很大的區(qū)別。
作用不同:ThreadLocal主要是用來(lái)創(chuàng)建線(xiàn)程本地變量,解決多線(xiàn)程并發(fā)訪(fǎng)問(wèn)時(shí)的變量沖突問(wèn)題;而Synchronized則是一種同步機(jī)制,用于保護(hù)共享資源,防止多線(xiàn)程之間的競(jìng)爭(zhēng)條件。
實(shí)現(xiàn)方式不同:ThreadLocal通過(guò)為每個(gè)線(xiàn)程創(chuàng)建獨(dú)立的變量副本,使得每個(gè)線(xiàn)程之間互不干擾,從而解決多線(xiàn)程訪(fǎng)問(wèn)共享變量時(shí)的線(xiàn)程安全問(wèn)題。而Synchronized則是通過(guò)互斥訪(fǎng)問(wèn)來(lái)實(shí)現(xiàn)同步的,即多個(gè)線(xiàn)程同時(shí)只能有一個(gè)線(xiàn)程訪(fǎng)問(wèn)共享資源。
應(yīng)用場(chǎng)景不同:ThreadLocal適用于需要在多個(gè)線(xiàn)程中使用獨(dú)立的變量的場(chǎng)景,如線(xiàn)程池中的線(xiàn)程狀態(tài)管理,以及Web應(yīng)用中的Session管理等;而Synchronized則適用于需要保護(hù)共享資源的場(chǎng)景,如多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)同一個(gè)數(shù)據(jù)結(jié)構(gòu),或者需要保證某個(gè)方法在同一時(shí)刻只能被一個(gè)線(xiàn)程訪(fǎng)問(wèn)等。
性能影響不同:ThreadLocal相對(duì)于Synchronized來(lái)說(shuō)性能更好,因?yàn)樗簧婕暗骄€(xiàn)程本地變量的訪(fǎng)問(wèn)和賦值操作,不需要進(jìn)行鎖競(jìng)爭(zhēng)和上下文切換等操作。而Synchronized則需要進(jìn)行鎖競(jìng)爭(zhēng)和上下文切換等操作,會(huì)對(duì)性能產(chǎn)生一定的影響。
ThreadLocal的優(yōu)點(diǎn):
- 線(xiàn)程安全:每個(gè)線(xiàn)程都擁有自己的變量副本,不會(huì)受到其他線(xiàn)程的影響,可以避免線(xiàn)程安全問(wèn)題。
- 性能高:ThreadLocal使用了空間換時(shí)間的方式,每個(gè)線(xiàn)程都有自己的變量副本,不需要進(jìn)行加鎖和解鎖操作,因此性能更高。
- 代碼簡(jiǎn)潔:使用ThreadLocal可以避免復(fù)雜的同步控制邏輯。
加鎖的優(yōu)點(diǎn):
- 保證數(shù)據(jù)一致性:通過(guò)加鎖可以保證共享資源在多線(xiàn)程環(huán)境下的正確性,避免出現(xiàn)數(shù)據(jù)不一致的情況。
- 線(xiàn)程同步:在加鎖過(guò)程中,線(xiàn)程會(huì)被阻塞,等待鎖的釋放,保證了線(xiàn)程同步。
ThreadLocal的缺點(diǎn):
- 內(nèi)存泄漏:ThreadLocal使用靜態(tài)的內(nèi)部Map來(lái)存儲(chǔ)變量副本,如果不及時(shí)清理,會(huì)導(dǎo)致內(nèi)存泄漏問(wèn)題(后續(xù)展開(kāi)介紹)。
- 難以調(diào)試:由于每個(gè)線(xiàn)程都有自己的變量副本,因此在調(diào)試過(guò)程中,需要考慮多個(gè)線(xiàn)程的情況,會(huì)增加調(diào)試的難度。
加鎖的缺點(diǎn):
- 性能問(wèn)題:在高并發(fā)情況下,加鎖會(huì)導(dǎo)致線(xiàn)程的阻塞,從而影響系統(tǒng)的性能。
- 容易導(dǎo)致死鎖:如果加鎖的操作不正確,可能會(huì)導(dǎo)致死鎖問(wèn)題,需要謹(jǐn)慎使用。
綜合來(lái)看,ThreadLocal適合處理線(xiàn)程私有的數(shù)據(jù),而加鎖適合處理共享的資源,具體應(yīng)該根據(jù)業(yè)務(wù)需求來(lái)選擇。
ThreadLocal的實(shí)現(xiàn)原理
ThreadLocal的內(nèi)部數(shù)據(jù)結(jié)構(gòu)
直接查看源碼:
Thread類(lèi):
public class Thread implements Runnable { //MAP ThreadLocal.ThreadLocalMap threadLocals = null; //用于父子線(xiàn)程變量同步, 后續(xù)介紹 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; }
ThreadLocal的set()方法:
public void set(T value) { //獲取當(dāng)前線(xiàn)程 Thread t = Thread.currentThread(); //封裝方法從獲取線(xiàn)程中的ThreadLocalMap //為什么封裝方法呢? 為了后面擴(kuò)展inheritableThreadLocals ThreadLocalMap map = getMap(t); //之前有創(chuàng)建過(guò), 直接set if (map != null) map.set(this, value); else //之前沒(méi)有創(chuàng)建, 新建Map并設(shè)置值 createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
從源碼中我們可以看到, 在set方法中, 我們先是獲取到當(dāng)前線(xiàn)程, 然后以當(dāng)前線(xiàn)程為入?yún)⒄{(diào)用getMap方法, 并獲取thread線(xiàn)程中的ThreadLocalMap屬性。如果map屬性不為空,則直接更新value值,如果map為空,則實(shí)例化threadLocalMap, 并將value值初始化。
那么threadLocalMap又是什么呢? 我們接著往下看。
ThreadLocalMap和ThreadLocalMap.Entry的實(shí)現(xiàn)
public class ThreadLocal<T> { static class ThreadLocalMap { //繼承弱應(yīng)用, 方便垃圾回收 static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } //數(shù)組, 用于存儲(chǔ)多組數(shù)據(jù) private Entry[] table; } }
從代碼我們可以看到, threadLocalMap是ThreadLocal中的一個(gè)靜態(tài)內(nèi)部類(lèi), 在threadLocalMap又維護(hù)了一個(gè)名叫table的Entry數(shù)組。
Entry是什么呢?
Entry是一組組數(shù)據(jù)對(duì), 而且繼承的弱引用。在Entry內(nèi)部使用ThreadLocal作為key,使用我們?cè)O(shè)置的value作為value。
key 就是 ThreadLocal,肯定不為空,但也是弱引用的。
也就是說(shuō),當(dāng) key 為 null 時(shí),說(shuō)明 ThreadLocal 已經(jīng)被回收了,對(duì)應(yīng)的 Entry 就應(yīng)該被清除了。
ThreadLocalMap.set()方法
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; //根據(jù)hashCode與長(zhǎng)度計(jì)算索引位置 int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; // 如果下標(biāo)沖突, 索引+1繼續(xù)查找 e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //找到直接返回值 if (k == key) { e.value = value; return; } if (k == null) { // key 為空, 說(shuō)明 對(duì)應(yīng)的 ThreadLocal 已經(jīng)回收了. // 可以復(fù)用當(dāng)前位置. // 有兩種情況:1\. entry 存在, 在這個(gè)過(guò)時(shí)位置的后面. 所以需要置換到這個(gè)位置 // 2.不存在, 直接放到這個(gè)位置 replaceStaleEntry(key, value, i); // 因?yàn)槭翘鎿Q, 所以size 要么不變,要么減少。 return; } } // 沒(méi)找到已存在的, 也沒(méi)找到可以替換的過(guò)時(shí). 則直接新建 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) // 如果沒(méi)有清除過(guò)時(shí) entry, 并且超過(guò)閾值. 則進(jìn)行先嘗試縮小,不行則擴(kuò)容 rehash(); }
在ThreadLocalMap中的set方法與構(gòu)造方法能看到以下代碼片段。
int i = key.threadLocalHashCode & (len-1)
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)
簡(jiǎn)而言之就是將threadLocalHashCode進(jìn)行一個(gè)位運(yùn)算(取模)得到索引i,threadLocalHashCode代碼如下。
public class ThreadLocal<T> { private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; /** * Returns the next hash code. */ private static int nextHashCode() { //自增 return nextHashCode.getAndAdd(HASH_INCREMENT); } }
因?yàn)閟tatic的原因,在每次new ThreadLocal()
時(shí)因?yàn)閠hreadLocalHashCode的初始化,會(huì)使threadLocalHashCode值自增一次,增量為0x61c88647。
0x61c88647是斐波那契散列乘數(shù),它的優(yōu)點(diǎn)是通過(guò)它散列(hash)出來(lái)的結(jié)果分布會(huì)比較均勻,可以很大程度上避免hash沖突。
有興趣可以深入研究下去, 這里就不過(guò)多贅述了, 這里這樣運(yùn)算就是為了避免索引下標(biāo)沖突。
總結(jié)一下:
對(duì)于某一ThreadLocal來(lái)講,他的索引值i是確定的,在不同線(xiàn)程之間訪(fǎng)問(wèn)時(shí)訪(fǎng)問(wèn)的是不同的table數(shù)組的同一位置即都為table[i],只不過(guò)這個(gè)不同線(xiàn)程之間的table是獨(dú)立的。
對(duì)于同一線(xiàn)程的不同ThreadLocal來(lái)講,這些ThreadLocal實(shí)例共享一個(gè)table數(shù)組,然后每個(gè)ThreadLocal實(shí)例在table中的索引i是不同的。
ThreadLocalMap.get()方法
public T get() { //獲取當(dāng)前線(xiàn)程 Thread t = Thread.currentThread(); //獲取ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) { //通過(guò)ThreadLocal獲取Entry ThreadLocalMap.Entry e = map.getEntry(this); //返回值 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //設(shè)置初始值--null return setInitialValue(); } private Entry getEntry(ThreadLocal<?> key) { //計(jì)算下標(biāo), 通過(guò)下標(biāo)從Entry數(shù)組中直接取值 int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else //索引沖突導(dǎo)致沒(méi)有查找到, 繼續(xù)查找 return getEntryAfterMiss(key, i, e); }
理解了set方法,get方法也就清楚明了,直接通過(guò)計(jì)算出索引直接從數(shù)組對(duì)應(yīng)位置讀取即可。
ThreadLocalMap.remove()方法
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
ThreadLocal的垃圾回收機(jī)制
ThreadLocal對(duì)象的垃圾回收機(jī)制比較特殊,主要涉及到兩個(gè)對(duì)象:ThreadLocal對(duì)象和ThreadLocalMap對(duì)象。
每個(gè)ThreadLocal對(duì)象都會(huì)在當(dāng)前線(xiàn)程的ThreadLocalMap中創(chuàng)建一個(gè)Entry對(duì)象,這個(gè)Entry對(duì)象包含了ThreadLocal對(duì)象和其對(duì)應(yīng)的值。當(dāng)ThreadLocal對(duì)象沒(méi)有被其他對(duì)象引用,并且當(dāng)前線(xiàn)程結(jié)束時(shí),這個(gè)ThreadLocal對(duì)象會(huì)被標(biāo)記為可回收的,并且被添加到一個(gè)特殊的ReferenceQueue中。
當(dāng)垃圾回收器掃描到ReferenceQueue中的ThreadLocal對(duì)象時(shí),它會(huì)將ThreadLocal對(duì)象對(duì)應(yīng)的Entry對(duì)象從ThreadLocalMap中刪除,并且清除Entry對(duì)象中對(duì)ThreadLocal對(duì)象和值的引用,從而使得ThreadLocal對(duì)象和值都能夠被回收。
需要注意的是,雖然ThreadLocal對(duì)象被回收了,但是它在ThreadLocalMap中對(duì)應(yīng)的Entry對(duì)象并沒(méi)有被立即清除,只有在下一次調(diào)用ThreadLocalMap的set()、get()或remove()方法時(shí)才會(huì)觸發(fā)Entry對(duì)象的清除操作。這是因?yàn)門(mén)hreadLocalMap中的Entry對(duì)象使用了弱引用,只有在下一次調(diào)用ThreadLocalMap時(shí)才會(huì)被垃圾回收器掃描到并被清除。
因此,使用ThreadLocal對(duì)象時(shí)需要注意,在不再需要使用ThreadLocal對(duì)象時(shí),應(yīng)該及時(shí)調(diào)用remove()方法,以便及時(shí)清除ThreadLocalMap中對(duì)應(yīng)的Entry對(duì)象,從而避免內(nèi)存泄漏。
ThreadLocal的使用場(chǎng)景
參數(shù)透?jìng)?/h3>
當(dāng)我們?cè)趯?xiě)API接口的時(shí)候,通常Controller層會(huì)接受來(lái)自前端的入?yún)?,?dāng)這個(gè)接口功能比較復(fù)雜的時(shí)候,可能我們調(diào)用的Service層內(nèi)部還調(diào)用了很多其他的很多方法,通常情況下,我們會(huì)在每個(gè)調(diào)用的方法上加上需要傳遞的參數(shù)。
但是如果我們將參數(shù)存入ThreadLocal中,那么就不用顯式的傳遞參數(shù)了,而是只需要ThreadLocal中獲取即可。
這個(gè)場(chǎng)景其實(shí)使用的比較少,一方面顯式傳參比較容易理解,另一方面我們可以將多個(gè)參數(shù)封裝為對(duì)象去傳遞。
全局存儲(chǔ)用戶(hù)信息(項(xiàng)目中用到)
在現(xiàn)在的系統(tǒng)設(shè)計(jì)中,前后端分離已基本成為常態(tài),分離之后如何獲取用戶(hù)信息就成了一件麻煩事,通常在用戶(hù)登錄后, 用戶(hù)信息會(huì)保存在Session或者Token中。這個(gè)時(shí)候,我們?nèi)绻褂贸R?guī)的手段去獲取用戶(hù)信息會(huì)很費(fèi)勁,拿Session來(lái)說(shuō),我們要在接口參數(shù)中加上HttpServletRequest對(duì)象,然后調(diào)用 getSession方法,且每一個(gè)需要用戶(hù)信息的接口都要加上這個(gè)參數(shù),才能獲取Session,這樣實(shí)現(xiàn)就很麻煩了。
在實(shí)際的系統(tǒng)設(shè)計(jì)中,我們肯定不會(huì)采用上面所說(shuō)的這種方式,而是使用ThreadLocal,我們會(huì)選擇在攔截器的業(yè)務(wù)中, 獲取到保存的用戶(hù)信息,然后存入ThreadLocal,那么當(dāng)前線(xiàn)程在任何地方如果需要拿到用戶(hù)信息都可以使用ThreadLocal的get()方法 (異步程序中ThreadLocal是不可靠的, 后續(xù)會(huì)出文章詳解)。
當(dāng)用戶(hù)登錄后,會(huì)將用戶(hù)信息存入Token中返回前端,當(dāng)用戶(hù)調(diào)用需要授權(quán)的接口時(shí),需要在header中攜帶 Token,然后攔截器中解析Token,獲取用戶(hù)信息,調(diào)用自定義的類(lèi)存入ThreadLocal中,當(dāng)請(qǐng)求結(jié)束的時(shí)候,將ThreadLocal存儲(chǔ)數(shù)據(jù)清空(這一點(diǎn)很重要,否則會(huì)產(chǎn)生內(nèi)存泄漏), 中間的過(guò)程無(wú)需再關(guān)注如何獲取用戶(hù)信息,只需要使用工具類(lèi)的get方法即可。
解決線(xiàn)程安全問(wèn)題
ThreadLocal的設(shè)計(jì)天然就做到了線(xiàn)程隔離。所以就不會(huì)出現(xiàn)線(xiàn)程安全問(wèn)題。
以上就是Java多線(xiàn)程之ThreadLocal淺析的詳細(xì)內(nèi)容,更多關(guān)于Java多線(xiàn)程ThreadLocal的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springboot基于assembly的服務(wù)化打包方案及spring boot部署方式
這篇文章主要介紹了Springboot基于assembly的服務(wù)化打包方案及springboot項(xiàng)目的幾種常見(jiàn)的部署方式,本文主要針對(duì)第二種部署方式提供一種更加友好的打包方案,需要的朋友可以參考下2017-12-12詳解Java 自動(dòng)裝箱與拆箱的實(shí)現(xiàn)原理
本篇文章主要介紹了詳解Java 自動(dòng)裝箱與拆箱的實(shí)現(xiàn)原理,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04SpringSecurity基于散列加密方案實(shí)現(xiàn)自動(dòng)登錄
本文主要介紹了SpringSecurity基于散列加密方案實(shí)現(xiàn)自動(dòng)登錄,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09java.sql.SQLException:?connection?holder?is?null錯(cuò)誤解決辦法
這篇文章主要給大家介紹了關(guān)于java.sql.SQLException:?connection?holder?is?null錯(cuò)誤的解決辦法,這個(gè)錯(cuò)誤通常是由于連接對(duì)象為空或未正確初始化導(dǎo)致的,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02Maven 倉(cāng)庫(kù)國(guó)內(nèi)鏡像源收藏(小結(jié))
這篇文章主要介紹了Maven 倉(cāng)庫(kù)國(guó)內(nèi)鏡像源收藏(小結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Spring Boot Actuator監(jiān)控的簡(jiǎn)單使用方法示例代碼詳解
這篇文章主要介紹了Spring Boot Actuator監(jiān)控的簡(jiǎn)單使用,本文通過(guò)實(shí)例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06maven 在執(zhí)行package,install,deploy時(shí)使用clean與不使用clean的不同之處
有時(shí)候用mvn install后,新改的內(nèi)容不生效,一定要后來(lái)使用mvn clean install 才生效,由于之前沒(méi)有做記錄,以及記不清是什么情況下才會(huì)出現(xiàn)的問(wèn)題,于是想看看clean和不clean的區(qū)別,感興趣的朋友跟隨小編一起看看吧2021-08-08Java進(jìn)階之Object類(lèi)及常用方法詳解
Object?類(lèi)是?Java?默認(rèn)提供的一個(gè)類(lèi),是所有?Java?類(lèi)的祖先類(lèi),每個(gè)類(lèi)都使用?Object?作為父類(lèi)。本文就來(lái)和大家聊聊Object類(lèi)的常用方法,希望對(duì)大家有所幫助2023-01-01java中的Io(input與output)操作總結(jié)(一)
所謂IO,也就是Input與Output的縮寫(xiě)。在java中,IO涉及的范圍比較大,這里主要討論針對(duì)文件內(nèi)容的讀寫(xiě),感興趣的朋友可以了解下2013-01-01