亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java中ThreadLocal使用原理及Synchronized區(qū)別

 更新時(shí)間:2023年05月17日 11:22:42   作者:倔強(qiáng)的不服  
ThreadLocal叫做線程變量,本文詳細(xì)的介紹了ThreadLocal使用原理及Synchronized區(qū)別,有需要的朋友可以參考一下,希望對(duì)你有所幫助。

一、ThreadLocal簡(jiǎn)介

ThreadLocal叫做線程變量,意思是ThreadLocal中填充的變量屬于當(dāng)前線程,該變量對(duì)其他線程而言是隔離的,也就是說(shuō)該變量是當(dāng)前線程獨(dú)有的變量。ThreadLocal為變量在每個(gè)線程中都創(chuàng)建了一個(gè)副本,那么每個(gè)線程可以訪問(wèn)自己內(nèi)部的副本變量。

ThreadLoal 變量,線程局部變量,同一個(gè) ThreadLocal 所包含的對(duì)象,在不同的 Thread 中有不同的副本。這里有幾點(diǎn)需要注意:

  • 因?yàn)槊總€(gè) Thread 內(nèi)有自己的實(shí)例副本,且該副本只能由當(dāng)前 Thread 使用。這是也是 ThreadLocal 命名的由來(lái)。
  • 既然每個(gè) Thread 有自己的實(shí)例副本,且其它 Thread 不可訪問(wèn),那就不存在多線程間共享的問(wèn)題。

ThreadLocal 提供了線程本地的實(shí)例。它與普通變量的區(qū)別在于,每個(gè)使用該變量的線程都會(huì)初始化一個(gè)完全獨(dú)立的實(shí)例副本。ThreadLocal 變量通常被private static修飾。當(dāng)一個(gè)線程結(jié)束時(shí),它所使用的所有 ThreadLocal 相對(duì)的實(shí)例副本都可被回收。

總的來(lái)說(shuō),ThreadLocal 適用于每個(gè)線程需要自己獨(dú)立的實(shí)例且該實(shí)例需要在多個(gè)方法中被使用,也即變量在線程間隔離而在方法或類間共享的場(chǎng)景

下圖可以增強(qiáng)理解:

圖1-1  ThreadLocal在使用過(guò)程中狀態(tài)

二、ThreadLocal與Synchronized的區(qū)別

ThreadLocal<T>其實(shí)是與線程綁定的一個(gè)變量。ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問(wèn)。

但是ThreadLocal與synchronized有本質(zhì)的區(qū)別:

1、Synchronized用于線程間的數(shù)據(jù)共享,而ThreadLocal則用于線程間的數(shù)據(jù)隔離。

2、Synchronized是利用鎖的機(jī)制,使變量或代碼塊在某一時(shí)該只能被一個(gè)線程訪問(wèn)。而ThreadLocal為每一個(gè)線程都提供了變量的副本,使得每個(gè)線程在某一時(shí)間訪問(wèn)到的并不是同一個(gè)對(duì)象,這樣就隔離了多個(gè)線程對(duì)數(shù)據(jù)的數(shù)據(jù)共享。

而Synchronized卻正好相反,它用于在多個(gè)線程間通信時(shí)能夠獲得數(shù)據(jù)共享。

一句話理解ThreadLocal,threadlocl是作為當(dāng)前線程中屬性ThreadLocalMap集合中的某一個(gè)Entry的key值Entry(threadlocl,value),雖然不同的線程之間threadlocal這個(gè)key值是一樣,但是不同的線程所擁有的ThreadLocalMap是獨(dú)一無(wú)二的,也就是不同的線程間同一個(gè)ThreadLocal(key)對(duì)應(yīng)存儲(chǔ)的值(value)不一樣,從而到達(dá)了線程間變量隔離的目的,但是在同一個(gè)線程中這個(gè)value變量地址是一樣的。

三、ThreadLocal的簡(jiǎn)單使用

直接上代碼:

public class ThreadLocaDemo {
    private static ThreadLocal<String> localVar = new ThreadLocal<String>();
    static void print(String str) {
        //打印當(dāng)前線程中本地內(nèi)存中本地變量的值
        System.out.println(str + " :" + localVar.get());
        //清除本地內(nèi)存中的本地變量
        localVar.remove();
    }
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_A");
                print("A");
                //打印本地變量
                System.out.println("after remove : " + localVar.get());
            }
        },"A").start();
        Thread.sleep(1000);
        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_B");
                print("B");
                System.out.println("after remove : " + localVar.get());
            }
        },"B").start();
    }
}
A :local_A
after remove : null
B :local_B
after remove : null

從這個(gè)示例中我們可以看到,兩個(gè)線程分表獲取了自己線程存放的變量,他們之間變量的獲取并不會(huì)錯(cuò)亂。這個(gè)的理解也可以結(jié)合圖1-1,相信會(huì)有一個(gè)更深刻的理解。

四、ThreadLocal的原理

要看原理那么就得從源碼看起。

4.1 ThreadLocal的set()方法:

 public void set(T value) {
        //1、獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        //2、獲取線程中的屬性 threadLocalMap ,如果threadLocalMap 不為空,
        //則直接更新要保存的變量值,否則創(chuàng)建threadLocalMap,并賦值
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // 初始化thradLocalMap 并賦值
            createMap(t, value);
    }

從上面的代碼可以看出,ThreadLocal  set賦值的時(shí)候首先會(huì)獲取當(dāng)前線程thread,并獲取thread線程中的ThreadLocalMap屬性。如果map屬性不為空,則直接更新value值,如果map為空,則實(shí)例化threadLocalMap,并將value值初始化。

那么ThreadLocalMap又是什么呢,還有createMap又是怎么做的,我們繼續(xù)往下看。大家最后自己再idea上跟下源碼,會(huì)有更深的認(rèn)識(shí)。

  static class ThreadLocalMap {
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }

可看出ThreadLocalMap是ThreadLocal的內(nèi)部靜態(tài)類,而它的構(gòu)成主要是用Entry來(lái)保存數(shù)據(jù) ,而且還是繼承的弱引用。在Entry內(nèi)部使用ThreadLocal作為key,使用我們?cè)O(shè)置的value作為value。詳細(xì)內(nèi)容要大家自己去跟。

//這個(gè)是threadlocal 的內(nèi)部方法
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //ThreadLocalMap 構(gòu)造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

4.2 ThreadLocal的get方法

    public T get() {
        //1、獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        //2、獲取當(dāng)前線程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //3、如果map數(shù)據(jù)不為空,
        if (map != null) {
            //3.1、獲取threalLocalMap中存儲(chǔ)的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果是數(shù)據(jù)為null,則初始化,初始化的結(jié)果,TheralLocalMap中存放key值為threadLocal,值為null
        return setInitialValue();
    }
private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

4.3 ThreadLocal的remove方法

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

 remove方法,直接將ThrealLocal 對(duì)應(yīng)的值從當(dāng)前相差Thread中的ThreadLocalMap中刪除。為什么要?jiǎng)h除,這涉及到內(nèi)存泄露的問(wèn)題。

實(shí)際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,弱引用的特點(diǎn)是,如果這個(gè)對(duì)象只存在弱引用,那么在下一次垃圾回收的時(shí)候必然會(huì)被清理掉。

所以如果 ThreadLocal 沒(méi)有被外部強(qiáng)引用的情況下,在垃圾回收的時(shí)候會(huì)被清理掉的,這樣一來(lái) ThreadLocalMap中使用這個(gè) ThreadLocal 的 key 也會(huì)被清理掉。但是,value 是強(qiáng)引用,不會(huì)被清理,這樣一來(lái)就會(huì)出現(xiàn) key 為 null 的 value。

ThreadLocal其實(shí)是與線程綁定的一個(gè)變量,如此就會(huì)出現(xiàn)一個(gè)問(wèn)題:如果沒(méi)有將ThreadLocal內(nèi)的變量刪除(remove)或替換,它的生命周期將會(huì)與線程共存。通常線程池中對(duì)線程管理都是采用線程復(fù)用的方法,在線程池中線程很難結(jié)束甚至于永遠(yuǎn)不會(huì)結(jié)束,這將意味著線程持續(xù)的時(shí)間將不可預(yù)測(cè),甚至與JVM的生命周期一致。舉個(gè)例字,如果ThreadLocal中直接或間接包裝了集合類或復(fù)雜對(duì)象,每次在同一個(gè)ThreadLocal中取出對(duì)象后,再對(duì)內(nèi)容做操作,那么內(nèi)部的集合類和復(fù)雜對(duì)象所占用的空間可能會(huì)開(kāi)始持續(xù)膨脹。

 4.4、ThreadLocal與Thread,ThreadLocalMap之間的關(guān)系  

圖4-1 Thread、THreadLocal、ThreadLocalMap之間啊的數(shù)據(jù)關(guān)系圖

從這個(gè)圖中我們可以非常直觀的看出,ThreadLocalMap其實(shí)是Thread線程的一個(gè)屬性值,而ThreadLocal是維護(hù)ThreadLocalMap

這個(gè)屬性指的一個(gè)工具類。Thread線程可以擁有多個(gè)ThreadLocal維護(hù)的自己線程獨(dú)享的共享變量(這個(gè)共享變量只是針對(duì)自己線程里面共享)

五、ThreadLocal 常見(jiàn)使用場(chǎng)景

如上文所述,ThreadLocal 適用于如下兩種場(chǎng)景

1、每個(gè)線程需要有自己?jiǎn)为?dú)的實(shí)例

2、實(shí)例需要在多個(gè)方法中共享,但不希望被多線程共享

對(duì)于第一點(diǎn),每個(gè)線程擁有自己實(shí)例,實(shí)現(xiàn)它的方式很多。例如可以在線程內(nèi)部構(gòu)建一個(gè)單獨(dú)的實(shí)例。ThreadLoca 可以以非常方便的形式滿足該需求。

對(duì)于第二點(diǎn),可以在滿足第一點(diǎn)(每個(gè)線程有自己的實(shí)例)的條件下,通過(guò)方法間引用傳遞的形式實(shí)現(xiàn)。ThreadLocal 使得代碼耦合度更低,且實(shí)現(xiàn)更優(yōu)雅。

場(chǎng)景

1)存儲(chǔ)用戶Session

一個(gè)簡(jiǎn)單的用ThreadLocal來(lái)存儲(chǔ)Session的例子:

private static final ThreadLocal threadSession = new ThreadLocal();
    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }

場(chǎng)景二、數(shù)據(jù)庫(kù)連接,處理數(shù)據(jù)庫(kù)事務(wù)

場(chǎng)景三、數(shù)據(jù)跨層傳遞(controller,service, dao)

每個(gè)線程內(nèi)需要保存類似于全局變量的信息(例如在攔截器中獲取的用戶信息),可以讓不同方法直接使用,避免參數(shù)傳遞的麻煩卻不想被多線程共享(因?yàn)椴煌€程獲取到的用戶信息不一樣)。

例如,用 ThreadLocal 保存一些業(yè)務(wù)內(nèi)容(用戶權(quán)限信息、從用戶系統(tǒng)獲取到的用戶名、用戶ID 等),這些信息在同一個(gè)線程內(nèi)相同,但是不同的線程使用的業(yè)務(wù)內(nèi)容是不相同的。

在線程生命周期內(nèi),都通過(guò)這個(gè)靜態(tài) ThreadLocal 實(shí)例的 get() 方法取得自己 set 過(guò)的那個(gè)對(duì)象,避免了將這個(gè)對(duì)象(如 user 對(duì)象)作為參數(shù)傳遞的麻煩。

比如說(shuō)我們是一個(gè)用戶系統(tǒng),那么當(dāng)一個(gè)請(qǐng)求進(jìn)來(lái)的時(shí)候,一個(gè)線程會(huì)負(fù)責(zé)執(zhí)行這個(gè)請(qǐng)求,然后這個(gè)請(qǐng)求就會(huì)依次調(diào)用service-1()、service-2()、service-3()、service-4(),這4個(gè)方法可能是分布在不同的類中的。這個(gè)例子和存儲(chǔ)session有些像。

package com.kong.threadlocal;
public class ThreadLocalDemo05 {
    public static void main(String[] args) {
        User user = new User("jack");
        new Service1().service1(user);
    }
}
class Service1 {
    public void service1(User user){
        //給ThreadLocal賦值,后續(xù)的服務(wù)直接通過(guò)ThreadLocal獲取就行了。
        UserContextHolder.holder.set(user);
        new Service2().service2();
    }
}
class Service2 {
    public void service2(){
        User user = UserContextHolder.holder.get();
        System.out.println("service2拿到的用戶:"+user.name);
        new Service3().service3();
    }
}
class Service3 {
    public void service3(){
        User user = UserContextHolder.holder.get();
        System.out.println("service3拿到的用戶:"+user.name);
        //在整個(gè)流程執(zhí)行完畢后,一定要執(zhí)行remove
        UserContextHolder.holder.remove();
    }
}
class UserContextHolder {
    //創(chuàng)建ThreadLocal保存User對(duì)象
    public static ThreadLocal<User> holder = new ThreadLocal<>();
}
class User {
    String name;
    public User(String name){
        this.name = name;
    }
}
執(zhí)行的結(jié)果:
service2拿到的用戶:jack
service3拿到的用戶:jack

場(chǎng)景四、Spring使用ThreadLocal解決線程安全問(wèn)題 

我們知道在一般情況下,只有無(wú)狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因?yàn)镾pring對(duì)一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全的“狀態(tài)性對(duì)象”采用ThreadLocal進(jìn)行封裝,讓它們也成為線程安全的“狀態(tài)性對(duì)象”,因此有狀態(tài)的Bean就能夠以singleton的方式在多線程中正常工作了。 

一般的Web應(yīng)用劃分為展現(xiàn)層、服務(wù)層和持久層三個(gè)層次,在不同的層中編寫(xiě)對(duì)應(yīng)的邏輯,下層通過(guò)接口向上層開(kāi)放功能調(diào)用。在一般情況下,從接收請(qǐng)求到返回響應(yīng)所經(jīng)過(guò)的所有程序調(diào)用都同屬于一個(gè)線程,如圖9-2所示。 

這樣用戶就可以根據(jù)需要,將一些非線程安全的變量以ThreadLocal存放,在同一次請(qǐng)求響應(yīng)的調(diào)用線程中,所有對(duì)象所訪問(wèn)的同一ThreadLocal變量都是當(dāng)前線程所綁定的。

下面的實(shí)例能夠體現(xiàn)Spring對(duì)有狀態(tài)Bean的改造思路:

代碼清單9-5  TopicDao:非線程安全

 
public class TopicDao {
   //①一個(gè)非線程安全的變量
   private Connection conn; 
   public void addTopic(){
        //②引用非線程安全變量
	   Statement stat = conn.createStatement();
	   …
   }

由于①處的conn是成員變量,因?yàn)閍ddTopic()方法是非線程安全的,必須在使用時(shí)創(chuàng)建一個(gè)新TopicDao實(shí)例(非singleton)。下面使用ThreadLocal對(duì)conn這個(gè)非線程安全的“狀態(tài)”進(jìn)行改造: 

代碼清單9-6  TopicDao:線程安全 

 
import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {
  //①使用ThreadLocal保存Connection變量
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();
public static Connection getConnection(){
	    //②如果connThreadLocal沒(méi)有本線程對(duì)應(yīng)的Connection創(chuàng)建一個(gè)新的Connection,
        //并將其保存到線程本地變量中。
if (connThreadLocal.get() == null) {
			Connection conn = ConnectionManager.getConnection();
			connThreadLocal.set(conn);
              return conn;
		}else{
              //③直接返回線程本地變量
			return connThreadLocal.get();
		}
	}
	public void addTopic() {
		//④從ThreadLocal中獲取線程對(duì)應(yīng)的
         Statement stat = getConnection().createStatement();
	}

不同的線程在使用TopicDao時(shí),先判斷connThreadLocal.get()是否為null,如果為null,則說(shuō)明當(dāng)前線程還沒(méi)有對(duì)應(yīng)的Connection對(duì)象,這時(shí)創(chuàng)建一個(gè)Connection對(duì)象并添加到本地線程變量中;如果不為null,則說(shuō)明當(dāng)前的線程已經(jīng)擁有了Connection對(duì)象,直接使用就可以了。這樣,就保證了不同的線程使用線程相關(guān)的Connection,而不會(huì)使用其他線程的Connection。因此,這個(gè)TopicDao就可以做到singleton共享了。 

當(dāng)然,這個(gè)例子本身很粗糙,將Connection的ThreadLocal直接放在Dao只能做到本Dao的多個(gè)方法共享Connection時(shí)不發(fā)生線程安全問(wèn)題,但無(wú)法和其他Dao共用同一個(gè)Connection,要做到同一事務(wù)多Dao共享同一個(gè)Connection,必須在一個(gè)共同的外部類使用ThreadLocal保存Connection。但這個(gè)實(shí)例基本上說(shuō)明了Spring對(duì)有狀態(tài)類線程安全化的解決思路。在本章后面的內(nèi)容中,我們將詳細(xì)說(shuō)明Spring如何通過(guò)ThreadLocal解決事務(wù)管理的問(wèn)題。

參考

ThreadLocal的應(yīng)用場(chǎng)景 - sw_kong - 博客園

百度安全驗(yàn)證

ThreadLocal使用場(chǎng)景分析 - 簡(jiǎn)書(shū)

ThreadLocal原理分析與使用場(chǎng)景 - 阿凡盧 - 博客園

ThreadLocal原理分析與使用場(chǎng)景 - 阿凡盧 - 博客園

ThreadLocal使用場(chǎng)景分析 - 簡(jiǎn)書(shū)

(轉(zhuǎn))spring里面對(duì)ThreadLocal的使用_迷茫之欲的博客-CSDN博客_spring threadlocal

到此這篇關(guān)于Java中ThreadLocal使用原理及Synchronized區(qū)別的文章就介紹到這了,更多相關(guān)Java ThreadLocal Synchronized 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java實(shí)現(xiàn)猜數(shù)字游戲

    java實(shí)現(xiàn)猜數(shù)字游戲

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)猜數(shù)字游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • springboot?使用websocket技術(shù)主動(dòng)給前端發(fā)送消息的實(shí)現(xiàn)

    springboot?使用websocket技術(shù)主動(dòng)給前端發(fā)送消息的實(shí)現(xiàn)

    這篇文章主要介紹了springboot?使用websocket技術(shù)主動(dòng)給前端發(fā)送消息的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • java遍歷properties文件操作指南

    java遍歷properties文件操作指南

    在java項(xiàng)目開(kāi)發(fā)過(guò)程中,使用properties文件作為配置基本上是必不可少的,有很多如系統(tǒng)配置信息,java如何遍歷properties文件呢,本文將詳細(xì)介紹,希望可以幫助到您
    2012-11-11
  • 一文搞懂Java中的注解和反射

    一文搞懂Java中的注解和反射

    這篇文章主要給大家介紹了關(guān)于Java中注解和反射的原理及使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • IDEA創(chuàng)建Spring項(xiàng)目無(wú)法選擇Java8的問(wèn)題及解決

    IDEA創(chuàng)建Spring項(xiàng)目無(wú)法選擇Java8的問(wèn)題及解決

    文章描述了在使用Spring創(chuàng)建項(xiàng)目時(shí)遇到的問(wèn)題,通過(guò)將服務(wù)器地址從https://start.spring.io/替換為https://start.aliyun.com/,成功解決了無(wú)法選擇Java8的問(wèn)題
    2025-01-01
  • Java中死鎖的原理實(shí)戰(zhàn)分析

    Java中死鎖的原理實(shí)戰(zhàn)分析

    這篇文章主要介紹了Java中死鎖的原理,結(jié)合具體案例形式分析了java死鎖形成的相關(guān)原理,需要的朋友可以參考下
    2019-08-08
  • 客戶端Socket與服務(wù)端ServerSocket串聯(lián)實(shí)現(xiàn)網(wǎng)絡(luò)通信

    客戶端Socket與服務(wù)端ServerSocket串聯(lián)實(shí)現(xiàn)網(wǎng)絡(luò)通信

    這篇文章主要為大家介紹了客戶端Socket與服務(wù)端ServerSocket串聯(lián)實(shí)現(xiàn)網(wǎng)絡(luò)通信的內(nèi)容詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-03-03
  • 詳解Java使用Jsch與sftp服務(wù)器實(shí)現(xiàn)ssh免密登錄

    詳解Java使用Jsch與sftp服務(wù)器實(shí)現(xiàn)ssh免密登錄

    這篇文章主要介紹了詳解Java使用Jsch與sftp服務(wù)器實(shí)現(xiàn)ssh免密登錄,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • 詳解Java如何實(shí)現(xiàn)在PDF中插入,替換或刪除圖像

    詳解Java如何實(shí)現(xiàn)在PDF中插入,替換或刪除圖像

    圖文并茂的內(nèi)容往往讓人看起來(lái)更加舒服,如果只是文字內(nèi)容的累加,往往會(huì)使讀者產(chǎn)生視覺(jué)疲勞。搭配精美的文章配圖則會(huì)使文章內(nèi)容更加豐富。那我們要如何在PDF中插入、替換或刪除圖像呢?別擔(dān)心,今天為大家介紹一種高效便捷的方法
    2023-01-01
  • Java簡(jiǎn)單從文件讀取和輸出的實(shí)例

    Java簡(jiǎn)單從文件讀取和輸出的實(shí)例

    下面小編就為大家?guī)?lái)一篇Java簡(jiǎn)單從文件讀取和輸出的實(shí)例。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-09-09

最新評(píng)論