標籤: 網頁設計公司

  • Fork/Join框架詳解

    Fork/Join框架詳解

    Fork/Join框架是Java 7提供的一個用於并行執行任務的框架,是一個把大任務分割成若干個小任務,最終匯總每個小任務結果后得到大任務結果的框架。Fork/Join框架要完成兩件事情:

    • 任務分割:首先Fork/Join框架需要把大的任務分割成足夠小的子任務,如果子任務比較大的話還要對子任務進行繼續分割

    • 執行任務併合並結果:分割的子任務分別放到雙端隊列里,然後幾個啟動線程分別從雙端隊列里獲取任務執行。子任務執行完的結果都放在另外一個隊列里,啟動一個線程從隊列里取數據,然後合併這些數據

    ForkJoinTask

    使用Fork/Join框架,首先需要創建一個ForkJoin任務。該類提供了在任務中執行fork和join的機制。通常情況下我們不需要直接集成ForkJoinTask類,只需要繼承它的子類,Fork/Join框架提供了兩個子類:

    • RecursiveAction
      用於沒有返回結果的任務
    • RecursiveTask
      用於有返回結果的任務

    ForkJoinPool

    ForkJoinTask需要通過ForkJoinPool來執行。

    任務分割出的子任務會添加到當前工作線程所維護的雙端隊列中,進入隊列的頭部。當一個工作線程的隊列里暫時沒有任務時,它會隨機從其他工作線程的隊列的尾部獲取一個任務(工作竊取算法);

    Fork/Join框架的實現原理

    ForkJoinPool由ForkJoinTask數組和ForkJoinWorkerThread數組組成,ForkJoinTask數組負責將存放程序提交給ForkJoinPool,而ForkJoinWorkerThread負責執行這些任務;

    ForkJoinTask的fork方法的實現原理

    當我們調用ForkJoinTask的fork方法時,程序會把任務放在ForkJoinWorkerThread的pushTask的workQueue中,異步地執行這個任務,然後立即返回結果,代碼如下:

    public final ForkJoinTask<V> fork() {
        Thread t;
        if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
            ((ForkJoinWorkerThread)t).workQueue.push(this);
        else
            ForkJoinPool.common.externalPush(this);
        return this;
    }

    pushTask方法把當前任務存放在ForkJoinTask數組隊列里。然後再調用ForkJoinPool的signalWork()方法喚醒或創建一個工作線程來執行任務。代碼如下:

    final void push(ForkJoinTask<?> task) {
        ForkJoinTask<?>[] a; ForkJoinPool p;
        int b = base, s = top, n;
        if ((a = array) != null) {    // ignore if queue removed
            int m = a.length - 1;     // fenced write for task visibility
            U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
            U.putOrderedInt(this, QTOP, s + 1);
            if ((n = s - b) <= 1) {
                if ((p = pool) != null)
                    p.signalWork(p.workQueues, this);
            }
            else if (n >= m)
                growArray();
        }
    }

    ForkJoinTask的join方法的實現原理

    Join方法的主要作用是阻塞當前線程並等待獲取結果。讓我們一起看看ForkJoinTask的join方法的實現,代碼如下:

    public final V join() {
        int s;
        if ((s = doJoin() & DONE_MASK) != NORMAL){
            reportException(s);
        }
        return getRawResult();
    }

    它首先調用doJoin方法,通過doJoin()方法得到當前任務的狀態來判斷返回什麼結果,任務狀態有4種:已完成(NORMAL)、被取消(CANCELLED)、信號(SIGNAL)和出現異常(EXCEPTIONAL);
    如果任務狀態是已完成,則直接返回任務結果;
    如果任務狀態是被取消,則直接拋出CancellationException;
    如果任務狀態是拋出異常,則直接拋出對應的異常;
    doJoin方法的實現,代碼如下:

    private int doJoin() {
        int s;
        Thread t;
        ForkJoinWorkerThread wt;
        ForkJoinPool.WorkQueue w;
        return (s = status) < 0 ? s :
                ((t = Thread.currentThread()) instanceof                                ForkJoinWorkerThread) ? (w = (wt =                                      (ForkJoinWorkerThread)t).workQueue).tryUnpush(this) && (s =                 doExec()) < 0 ? s : wt.pool.awaitJoin(w, this, 0L) :                externalAwaitDone();
    }

    doExec() :

    final int doExec() {
        int s; 
        boolean completed;
        if ((s = status) >= 0) {
            try {
                completed = exec();
            } catch (Throwable rex) {
                return setExceptionalCompletion(rex);
            }
            if (completed){
                s = setCompletion(NORMAL);
            }
        }
        return s;
    }

    在doJoin()方法里,首先通過查看任務的狀態,看任務是否已經執行完成,如果執行完成,則直接返回任務狀態;如果沒有執行完,則從任務數組裡取出任務並執行。如果任務順利執行完成,則設置任務狀態為NORMAL,如果出現異常,則記錄異常,並將任務狀態設置為EXCEPTIONAL

    Fork/Join框架的異常處理

    ForkJoinTask在執行的時候可能會拋出異常,但是我們沒辦法在主線程里直接捕獲異常,所以ForkJoinTask提供了isCompletedAbnormally()方法來檢查任務是否已經拋出異常或已經被取消了,並且可以通過ForkJoinTask的getException方法獲取異常。代碼如下:

    if(task.isCompletedAbnormally())
    {
        System.out.println(task.getException());
    }

    getException方法返回Throwable對象,如果任務被取消了則返回CancellationException。如果任務沒有完成或者沒有拋出異常則返回null:

    public final Throwable getException() {
        int s = status & DONE_MASK;
        return ((s >= NORMAL) ? null :
            (s == CANCELLED) ? new CancellationException() :
            getThrowableException());
    }

    DEMO

    需求:求1+2+3+4的結果
    分析:Fork/Join框架首先要考慮到的是如何分割任務,如果希望每個子任務最多執行兩個數的相加,那麼我們設置分割的閾值是2,由於是4個数字相加,所以Fork/Join框架會把這個任務fork成兩個子任務,子任務一負責計算1+2,子任務二負責計算3+4,然後再join兩個子任務的結果。因為是有結果的任務,所以必須繼承RecursiveTask,實現代碼如下:

    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.Future;
    import java.util.concurrent.RecursiveTask;
    
    /**
     *
     * @author aikq
     * @date 2018年11月21日 20:37
     */
    public class ForkJoinTaskDemo {
    
        public static void main(String[] args) {
            ForkJoinPool pool = new ForkJoinPool();
            CountTask task = new CountTask(1,4);
            Future<Integer> result = pool.submit(task);
            try {
                System.out.println("計算結果=" + result.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
    
    class CountTask extends RecursiveTask<Integer>{
        private static final long serialVersionUID = -7524245439872879478L;
    
        private static final int THREAD_HOLD = 2;
    
        private int start;
        private int end;
    
        public CountTask(int start,int end){
            this.start = start;
            this.end = end;
        }
    
        @Override
        protected Integer compute() {
            int sum = 0;
            //如果任務足夠小就計算
            boolean canCompute = (end - start) <= THREAD_HOLD;
            if(canCompute){
                for(int i=start;i<=end;i++){
                    sum += i;
                }
            }else{
                int middle = (start + end) / 2;
                CountTask left = new CountTask(start,middle);
                CountTask right = new CountTask(middle+1,end);
                //執行子任務
                left.fork();
                right.fork();
                //獲取子任務結果
                int lResult = left.join();
                int rResult = right.join();
                sum = lResult + rResult;
            }
            return sum;
        }
    }

    通過這個例子,我們進一步了解ForkJoinTask,ForkJoinTask與一般任務的主要區別在於它需要實現compute方法,在這個方法里,首先需要判斷任務是否足夠小,如果足夠小就直接執行任務。如果不足夠小,就必須分割成兩個子任務,每個子任務在調用fork方法時,又會進入compute方法,看看當前子任務是否需要繼續分割成子任務,如果不需要繼續分割,則執行當前子任務並返回結果。使用join方法會等待子任務執行完並得到其結果

    本文由博客一文多發平台 發布!

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • paper sharing :學習特徵演化的數據流

    paper sharing :學習特徵演化的數據流

    特徵演化的數據流

        數據流學習是近年來機器學習與數據挖掘領域的一個熱門的研究方向,數據流的場景和靜態數據集的場景最大的一個特點就是數據會發生演化,關於演化數據流的研究大多集中於概念漂移檢測(有監督學習),概念/聚類演化分析(無監督學習),然而,人們往往忽略了一個經常出現的演化場景:特徵演化。大多數研究都考慮數據流的特徵空間是固定的,然而,在很多場景下這一假設並不成立:例如,當有限壽命傳感器收集的數據被新的傳感器替代時,這些傳感器對應的特徵將發生變化。

        今天要分享的文章出自周志華的實驗室《Learning with Feature Evolvable Streams》(NIPS 2017),它提出了一個新的場景,即在數據流中會有特徵消亡也會有新特徵出現。當出現新的特徵空間時,我們並不直接拋棄之前學到的模型並在新的數據上重新創建模型,而是嘗試恢復消失的特徵來提升模型的表現。具體來說,通過從恢復的特徵和新的特徵空間中分別學習兩個模型。為了從恢復的特徵中獲得提升,論文中提出了兩種集成策略:第一種方法是合併兩個模型的預測結果;第二種是選擇最佳的預測模型。下面我們具體來理解特徵演化數據流以及論文中提出的一些有趣的方法吧~

    paper link:

     

    什麼是特徵演化數據流?

        在很多現實的任務中,數據都是源源不斷收集的,關於數據流學習的研究近年來受到越來越多的關注,雖然已經有很多有效的算法針對特定的場景對數據流進行挖掘,但是它們都基於一個假設就是數據流中數據的特徵空間是穩定的。不幸的是,這一假設在很多場景下都不滿足。針對特徵演化的場景,最直接的想法就是利用新的特徵空間的數據學習一個新的模型,但是這一方法有很多問題:首先,當新的特徵剛出現的時候,只有很少的數據樣本來描述這些信息,訓練樣本並不足夠去學習一個新的模型;其次,包含消失特徵的舊模型被直接丟棄了,其中可能包含對當前數據有用的信息。論文中定義了一種特徵演化數據流的場景:一般情況下,特徵不會任意改變,而在一些重疊時期,新特徵和舊特徵都存在,如下圖所示:

        其中,T1階段,原始特徵集都是有效的,B1階段出現了新的特徵集,T2階段原始特徵集消失,只有新的特徵集。

        論文提出的方法是通過使用重疊(B1)階段來發現新舊特徵之間的關係,嘗試學習新特徵到舊特徵的一個映射,這樣就可以通過重構舊特徵並使用舊模型對新數據進行預測

    問題描述

        論文中着重解決的是分類和回歸任務,在每一輪學習過程中,對每一個實例進行預測,結合它的真實標籤會得到一個loss(反映預測和真實標籤的差異),我們將上面提到的T1+B1+T的過程稱為一個周期,每個周期中只包含兩個特徵空間,所以,之後的研究主要關注一個周期內的模型的學習,而且,我們假設一個周期內的舊特徵會同時消失。定義Ω1和Ω2分別表示兩個特徵空間S1和S2上的線性模型,並定義映射,定義第i維特徵在第t輪的預測函數為線性模型,。損失函數是凸的,最直接的方式是使用在線梯度下降來求解w,但是在數據流上不適用。

     

    方法介紹

        上文提到的基本算法的主要限制是在第1,…T1輪學習的模型在T1+1,…T1+T2時候被忽略了,這是因為T1之後數據的特徵空間改變了,我們無法直接應用原來的模型。為了解決這一問題,我們假設新舊特徵空間之間有一種特定的關係:,我們嘗試通過重疊階段B1來學習這種關係。學習兩組特徵之間的關係的方法很多,如多元回歸,數據流多標籤學習等。但是在當前的場景下,由於重疊階段特別短,學習一個複雜的關係模型是不現實的。所以我們採用線性映射來近似。定義線性映射的係數矩陣為M,那麼在B1階段,M的估計可以基於如下的目標方程:

    M的最優解可以解得:

        然後,當我觀測到S2空間得數據,就可以通過M將其轉化到S1空間,並應用舊模型對其進行預測。

    除了學習這個關係映射之外,我們得算法主要包括兩個部分:

    1. 在T1-B1+1,…T1階段,我們學習兩個特徵空間之間得關係;

    2. 在T1之後,我們使用新特徵空間的數據轉化后的原特徵空間數據,持續更新舊模型以提升它的預測效果,然後集成兩個模型進行預測。

     

    預測結果集成

        論文中提出兩種集成方法,第一種是加權組合,即將兩個模型的預測結果求加權平均,權重是基於exponential of the cumulative loss。

    其中

        這種權重的更新規則表明,如果上一輪模型的損失較大,下一輪模型的權值將以指數速度下降,這是合理的,可以得到很好的理論結果。

        第二種集成方法是動態選擇。

        上面提到的組合的方法結合了幾個模型來提升整體性能,通常來說,組合多個分類器的表現會比單分類器的效果要好,但是,這基於一個重要的假設就是每個基分類器的表現不能太差(如,在Adaboost中,基分類器的預測精度不應低於0.5)。然而在這個問題中,由於新特徵空間剛出現的時候訓練集較小,訓練的模型不好,因此可能並不適合用組合的方法來預測,相反,用動態選擇最優模型的方法反而能獲得好的效果。

    有趣的靈魂在等你長按二維碼識別

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 【併發編程】Java中的原子操作

    【併發編程】Java中的原子操作

    什麼是原子操作

    原子操作是指一個或者多個不可再分割的操作。這些操作的執行順序不能被打亂,這些步驟也不可以被切割而只執行其中的一部分(不可中斷性)。舉個列子:

    //就是一個原子操作
    int i = 1;
    
    //非原子操作,i++是一個多步操作,而且是可以被中斷的。
    //i++可以被分割成3步,第一步讀取i的值,第二步計算i+1;第三部將最終值賦值給i
    i++;

    Java中的原子操作

    在Java中,我們可以通過同步鎖或者CAS操作來實現原子操作。

    CAS操作

    CAS是Compare and swap的簡稱,這個操作是硬件級別的操作,在硬件層面保證了操作的原子性。CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什麼都不做。Java中的sun.misc.Unsafe類提供了compareAndSwapIntcompareAndSwapLong等幾個方法實現CAS。

    另外,在jdk的atomic包下面提供了很多基於CAS實現的原子操作類,見下圖:

    下面我們就使用其中的AtomicInteger來看看怎麼使用這些原子操作類。

    package com.csx.demo.spring.boot.concurrent.atomic;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class AtomicIntegerDemo {
    
        private static int THREAD_COUNT = 100;
    
        public static void main(String[] args) throws InterruptedException {
    
    
            NormalCounter normalCounter = new NormalCounter("normalCounter",0);
            SafeCounter safeCounter = new SafeCounter("safeCounter",0);
            List<Thread> threadList = new ArrayList<>();
    
            for (int i = 0; i < THREAD_COUNT ; i++) {
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 0; j < 10000; j++) {
                            normalCounter.add(1);
                            safeCounter.add(1);
                        }
                    }
                });
                threadList.add(thread);
            }
    
            for (Thread thread : threadList) {
                thread.start();
            }
            for (Thread thread : threadList) {
                thread.join();
            }
            System.out.println("normalCounter:"+normalCounter.getCount());
            System.out.println("safeCounter:"+safeCounter.getCount());
        }
    
    
        public static class NormalCounter{
            private String name;
            private Integer count;
    
            public NormalCounter(String name, Integer count) {
                this.name = name;
                this.count = count;
            }
    
            public void add(int delta){
                this.count = count+delta;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public Integer getCount() {
                return count;
            }
    
            public void setCount(Integer count) {
                this.count = count;
            }
        }
    
        public static class SafeCounter{
            private String name;
            private AtomicInteger count;
    
            public SafeCounter(String name, Integer count) {
                this.name = name;
                this.count = new AtomicInteger(count);
            }
    
            public void add(int delta){
                count.addAndGet(delta);
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public int getCount() {
                return count.get();
            }
    
            public void setCount(Integer count) {
                this.count.set(count);
            }
        }
    
    }

    上面的代碼中,我們分別創建了一個普通的計數器和一個原子操作的計數器(使用AtomicInteger進行計數)。然後創建了100個線程,每個線程進行10000次計數。理論上線程執行完之後,計數器的值都是1000000,但是結果如下:

    normalCounter:496527
    safeCounter:1000000

    每次執行,普通計數器的值都是不一樣的,而使用AtomicInteger進行計數的計數器都是1000000。

    CAS操作存在的問題

    • ABA問題:因為CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。

    從Java1.5開始JDK的atomic包里提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設置為給定的更新值。

    • 循環時間長開銷大:自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支持處理器提供的pause指令那麼效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序衝突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。

    • 只能保證一個共享變量的原子操作:當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合併成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合併一下ij=2a,然後用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作。

    使用鎖來保證原子操作

    還是以上面的列子為列,普通的計數器我們只需要在計數方法上加鎖就行了:

    public synchronized void  add(int delta){
      this.count = count+delta;
    }

    執行結果如下:

    normalCounter:1000000
    safeCounter:1000000

    兩個計數器都能拿到正確的結果

    CPU是怎麼實現原子操作的

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 關於GC(中):Java垃圾回收相關基礎知識

    關於GC(中):Java垃圾回收相關基礎知識

    Java內存模型

    (圖源: )

    區域名 英文名 訪問權限 作用 備註
    程序計數器 Program Counter Register 線程隔離 標記待取的下一條執行的指令 執行Native方法時為空; JVM規範中唯一不會發生OutOfMemoryError的區域
    虛擬機棧 VM Stack 線程隔離 每個Java方法執行時創建,用於存儲局部變量表,操作棧,動態鏈接,方法出口等信息 方法執行的內存模型
    本地方法棧 Native Method Stack 線程隔離 Native方法執行時使用 JVM規範沒有強制規定,如Hotspot將VM和Native兩個方法棧合二為一
    Java堆 Java Heap 線程共享 存放對象實例 更好的回收內存 vs 更快的分配內存
    方法區 Method Area 線程共享 存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據 JVM規範不強制要求做垃圾收集
    運行時常量池 Runtime Constant Pool 線程共享 方法區的一部分
    直接內存 Direct Memory 堆外內存,通過堆的DirectByteBuffer訪問 不是運行時數據區的一部分,但也可能OutOfMemoryError

    對象的創建——new的時候發生了什麼

    討論僅限於普通Java對象,不包括數組和Class對象。

    1. 常量池查找類的常量引用,如果沒有先做類加載
    2. 分配內存,視堆內存是否是規整(由垃圾回收器是否具有壓縮功能而定)而使用“指針碰撞”或“空閑列表”模式
    3. 內存空間初始化為零值,可能提前在線程創建時分配TLAB時做初始化
    4. 設置必要信息,如對象是哪個類的示例、元信息、GC分代年齡等
    5. 調用<init>方法

    垃圾回收器總結

    垃圾回收,針對的都是堆。

    分代

    • 新生代:適合使用複製算法, 以下三個區一般佔比為8:1:1
      • Eden 新對象誕生區
      • From Survivor 上一次GC的倖存者(見“GC種類-minor GC”)
      • To Survivor 本次待存放倖存者的區域
    • 老年代:存活時間較久的,大小較大的對象,因此使用標記-整理或標記-清除算法比較合適
    • 永久代:存放類信息和元數據等不太可能回收的信息。Java8中被元空間(Metaspace)代替,不再使用堆,而是物理內存。

    分代的原因

    • 不同代的對象生命周期不同,可以針對性地使用不同的垃圾回收算法
    • 不同代可以分開進行回收

    回收算法

    名稱 工作原理 優點 缺點
    標記-清除 對可回收對對象做一輪標記,標記完成后統一回收被標記的對象 易於理解,內存利用率高 效率問題;內存碎片;分配大對象但無空間時提前GC
    複製 內存均分兩塊,只使用其中一塊。回收時將這一塊存活對象全部複製到另一塊 效率高 可用空間減少; 空間不夠時需老年代分配擔保
    標記-整理 對可回收對對象做一輪標記,標記完成后將存活對象統一左移,清理掉邊界外內存 內存利用率高 效率問題

    標記-X算法適用於老年代,複製算法適用於新生代。

    GC種類

    • Minor GC,只回收新生代,將Eden和From Survivor區的存活對象複製到To Survivor
    • Major GC,清理老年代。但因為伴隨着新生代的對象生命周期升級到老年代,一般也可認為伴隨着FullGC。
    • FullGC,整個堆的回收
    • Mixed GC,G1特有,可能會發生多次回收,可以參考

    垃圾回收器小結

    垃圾回收器名稱 特性 目前工作分代 回收算法 可否與Serial配合 可否與ParNew配合 可否與ParallelScavenge配合 可否與SerialOld配合 可否與ParallelOld配合 可否與CMS配合 可否與G1配合
    Serial 單線程 新生代 複製 Y N Y N/A
    ParNew 多線程 新生代 複製 N N Y N/A
    ParallelScavenge 多線程, 更關注吞吐量可調節 新生代 複製 N N Y N/A
    SerialOld 單線程 老年代 標記-整理 Y Y N N/A
    ParallelOld 多線程 老年代 標記-整理 N N Y N/A
    CMS 多線程,併發收集,低停頓。但無法處理浮動垃圾,標記-清除會產生內存碎片較多 老年代 標記-清除 Y Y N Y N/A
    G1 并行併發收集,追求可預測但回收時間,整體內存模型有所變化 新生代/老年代 整體是標記-整理,局部(兩Region)複製 N N N N N N

    在本系列的上一篇文章中,減少FullGC的方式是使用G1代替CMS,計劃在下一篇文章中對比CMS和G1的區別。

    理解GC日誌

    只舉比較簡單的例子,具體各項的格式視情況分析,不同回收器也會有差異。

    2019-11-22T10:28:32.177+0800: 60188.392: [GC (Allocation Failure) 2019-11-22T10:28:32.178+0800: 60188.392: [ParNew: 1750382K->2520K(1922432K), 0.0312604 secs] 1945718K->198045K(4019584K), 0.0315892 secs] [Times: user=0.09 sys=0.01, real=0.03 secs]

    開始時間-(方括號[)-發生區域(ParNew,命名和GC回收器有關)-回收前大小-回收后大小-(方括號])-GC前堆已使用容量-GC后堆已使用容量大小-回收時間-使用時間詳情(用戶態時間-內核時間-牆上時鐘時間)

    注意這裏沒有包括“2019-11-22T10:28:32.177+0800: 60188.392: [GC (Allocation Failure)”這部分的分析。

    可借鑒的編程模式

    對象分配的併發控制

    對象創建是很頻繁的,在線程共享的堆中會遇到併發的問題。兩種解決辦法:

    1. 同步鎖定:CAS+失敗重試,確保原子性
    2. 堆中預先給每個線程劃分一小塊內存區域——本地線程分配緩衝(TLAB),TLAB使用完並分配新的TLAB時才做同步鎖定。可看作1的優化。

    CAS: Conmpare And Swap,用於實現多線程同步的原子指令。 將內存位置的內容與給定值進行比較,只有在相同的情況下,將該內存位置的內容修改為新的給定值。關於CAS可以參考:

    對象訪問的定位方式

    前提條件:通過上本地變量表的reference訪問中的對象及它在方法區的對象類型數據(類信息)
    主流的兩種方式,這兩種方式各有優點,可以看出方式2是方式1的優化,但並不是全面超越方式1,無法完全取代。
    這裏可以看到要權衡垃圾回收和訪問速度兩方面。

    方式1: 直接指針訪問實例數據

    reference直接存放對象實例地址,只需要一次訪問即可,執行效率較高。

    方式2: 使用句柄池

    reference中地址穩定,對象被移動時只需要改句柄池的地址。相對的,訪問實例需要兩次指針定位。

    參考資料

    1. 周志明.著《深入理解JAVA虛擬機》

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • 三個月(敏捷)項目收穫

    三個月(敏捷)項目收穫

     

     

    項目背景

    客戶已有運行多年的官網老站(PC端),想在今年對老站進行一次UI全面更新、功能全部平移的升級,對接新的運營後端,然後建立官網小程序端且與官網PC端進行聯動,使得品牌自有渠道能夠更加全面化。

     

    挑戰

    • 時間緊。五月份進行Inception Workshop,確定項目交付範圍與架構方案。官網六月初開始開發,小程序八月份開始開發,整個項目九月中旬必須上線。
    • 系統集成和數據遷移。系統需要對接客戶的CRM,對接3個服務商,需要對老官網歷史數據(訂單、會員等)進行遷移。
    • 多團隊溝通。小程序設計稿由第三方提供,因此多出了溝通、確認的時間,以及把控第三方交付的時間,以避免交付進度的影響。

     

    迭代計劃

    Inception Workshop一結束,差不多就開始整理整個項目涉及的故事和技術卡,按照兩周一迭代進行迭代計劃安排並與客戶確認,每個迭代第一周周三安排跟客戶showcase上一周的預定的交付結果,得到反饋並安排進行改進。官網項目比較順利,改造自定義了一下SSR框架就能開始進行開發,並且因為歷史原因,還能享受到上一個項目遺留的一些福利,當然也少不了一些坑。

    小程序的時間比較緊,相當於整個複製了一遍官網的功能,主要是前端任務,後端可以復用官網後端,因此一開始就給團隊同學同步到整個項目的情況,讓大家有一個大概的心理準備。然後就是與官網類似的處理,整個交付內容進行迭代排期並與客戶確認,前期盡量能多做一些,避免後期怎麼努力都無法完成的囧鏡。

     

    項目進行時

    整個項目的過程中,PM會根據迭代完成情況靈活的找外援加入項目進行支援,以免交付延期。每日的站會(Standup Meeting)更新,讓團隊能對當前進度有一個大概的了解以及同步一些突發信息。定期的回顧會議(Retrospective Meeting)能暴露團隊內部問題,將風險扼殺於苗頭,鼓勵能為團隊帶來正向幫助的行為,及時停止不好的做法。

    迭代會議(IPM)能讓團隊對下一個迭代具體要做的事情有一個詳細的了解,進行大致的估點,以便check開發進度情況。技術人員定期的CodeReview成為一個大家交流的時段,發現風險,指出問題,互相提高,還可以幫助新人快速的融入團隊。

    根據團隊內部人員情況,可以定期進行一對一溝通,了解個人訴求或是給與近況反饋都是一個不錯的渠道。TL應考慮團隊內部人員提升自己的訴求,在一些安排上給與傾斜和鼓勵,發現問題也需要提前制止。

     

    不足之處

    • 後期對卡牆(Jira)的管理鬆懈。導致有些問題反覆修改,且丟失context
    • 項目對運營後台有一些的定製化配置,沒有提前準備運營需要了解的後台操作資料和培訓,導致後期花費大量精力幫助運營進行後台配置與更新
    • 人員(QA)變動頻繁。公司處於高速發展階段,項目經歷了4個QA,因此有些context可能丟失,測試不到位,導致項目上線出了一些低級問題。比如上線后發現部分瀏覽器有支付兼容問題
    • 甲乙方定位太角色化,不能站在專業角度評估客戶需求(項目做完感覺都一樣,客戶是爸爸)
    • 與第三方合作交付產物管控不到位,導致第三方設計稿的延遲影響到我們的交付計劃
    • 與客戶溝通的需求,後面有一些沒有進行郵件確認,導致交付驗收階段因一些需求上的問題產生不愉快(這個完全沒必要的)
    • 對第三方系統的了解不充分和集成系統的需求整理不清晰導致後續一系列的開發、測試都不到位,以致上線出了不可控的問題

     

    項目總結

    • 提前評估項目的風險點,且在項目進行過程中持續維護,後期安排足夠的時間進行調研與分析
    • 與第三方合作一定要有自己的規劃,並且將定好的規劃提前與第三方確認時間,然後派人提前專門細緻的了解第三方需求詳細點,確定好具體的業務場景,再來規劃己方與第三方的具體集成的點。此外,在進行的過程中,還應注意定時檢查合作進度,管控風險
    • 與客戶溝通的所有需求都要進行郵件的二次確認,一個是能夠對所有需求來源有所記錄,另一個是能避免後面的不必要的消耗
    • 開發管理不能鬆懈,盡量做到所有的改動都能有卡,能夠進行追溯
    • 對後期交付時所需要的資料提前準備,對需要進行培訓的人員提前約好時間進行溝通培訓

    在前端這塊的管理上,做的還不夠。前期經常codereview,然後效果都還不錯,讓我有了一些錯覺就是當前團隊趨於穩定,中後期即便是加班比較多,大家氣氛這塊我覺得都還好。不過在項目後期的時候,有些疏於管理,然後大家有些人也被分配到了其他項目,和同事們的交流不夠,沒有及時的顧及到一些個人情緒,這塊是可以加強的。

    作為一個Lead,不論同事是否還在一個項目都應該及時的去了解近況,給與自己力所能及的幫助,這樣才能產生向心力,以幫助一些比較迷茫的同學找到方向,看見燈塔。

    整個項目時間不長,得失還是挺多的,不論是管理還是技術上,都會有一些心得。然後項目的ROI還不錯,得到公司領導的肯定,最後客戶那邊的反饋也還不錯,算是對大家努力的一種認可。

    ps: 及時總結,靜心沉澱;如風少年,砥礪前行。

    如想了解更多,請移步

    歡迎關注我的公眾號 “和F君一起xx”

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 微服務框架 – Jimu(積木) 升級 1.0.0 支持 .Net Core 3.0

    微服務框架 – Jimu(積木) 升級 1.0.0 支持 .Net Core 3.0

    如果不知道 Jimu(積木) 是啥,請移步
    這次升級除了支持 .Net Core 3.0 還新增部分功能,如 REST, 鏈路跟蹤等,以下為詳細;

    一、功能列表

    功能 說明 Jimu 1.0.0 Jimu 0.6.0
    平台 .Net Core 2.1
    .Net Core 3.0
    服務註冊與發現 consul
    網關 Asp.Net Core Web
    RPC DotNetty
    鑒權 JWT
    負載均衡 輪訓
    容錯策略 重試
    容器 docker
    路由配置 Attribute註解
    日誌記錄 log4net
    nlog
    文檔 swagger
    鏈路跟蹤 skywalking
    REST Attribute註解
    健康監測 心跳
    文件上存下載 多文件上存,單文件下載
    跳轉 在服務端跳轉到指定url
    ORM Dapper
    DDD MiniDDD

    二、建議用積木結合 docker 搭建分佈式架構

    三、swagger

    四、skywalking

    拓撲圖: user -> jimu_apigateway -> jimu_order -> jimu_user

    Trace 跟蹤

    五、網關

    服務器

    微服務

    微服務詳細

    六、源碼

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 類加載器 – ClassLoader詳解

    獲得ClassLoader的途徑

    • 獲得當前類的ClassLoader
      • clazz.getClassLoader()
    • 獲得當前線程上下文的ClassLoader
      • Thread.currentThread().getContextClassLoader();
    • 獲得系統的ClassLoader
      • ClassLoader.getSystemClassLoader()
    • 獲得調用者的ClassLoader
      • DriverManager.getCallerClassLoader

    ClassLoader源碼解析

    概述

    類加載器是用於加載類的對象,ClassLoader是一個抽象類。如果我們給定了一個類的二進制名稱,類加載器應嘗試去定位或生成構成定義類的數據。一種典型的策略是將給定的二進制名稱轉換為文件名,然後去文件系統中讀取這個文件名所對應的class文件。

    每個Class對象都會包含一個定義它的ClassLoader的一個引用。

    數組類的Class對象,不是由類加載器去創建的,而是在Java運行期JVM根據需要自動創建的。對於數組類的類加載器來說,是通過Class.getClassLoader()返回的,與數組當中元素類型的類加載器是一樣的;如果數組當中的元素類型是一個原生類型,數組類是沒有類加載器的【代碼一】。

    應用實現了ClassLoader的子類是為了擴展JVM動態加載類的方式。

    類加載器典型情況下時可以被安全管理器所使用去標識安全域問題。

    ClassLoader類使用了委託模型來尋找類和資源,ClassLoader的每一個實例都會有一個與之關聯的父ClassLoader,當ClassLoader被要求尋找一個類或者資源的時候,ClassLoader實例在自身嘗試尋找類或者資源之前會委託它的父類加載器去完成。虛擬機內建的類加載器,稱之為啟動類加載器,是沒有父加載器的,但是可以作為一個類加載器的父類加載器【雙親委託機制】。

    支持併發類加載的類加載器叫做并行類加載器,要求在初始化期間通過ClassLoader.registerAsParallelCapable 方法註冊自身,ClassLoader類默認被註冊為可以并行,但是如果它的子類也是并行加載的話需要單獨去註冊子類。

    在委託模型不是嚴格的層次化的環境下,類加載器需要并行,否則類加載會導致死鎖,因為加載器的鎖在類加載過程中是一直被持有的。

    通常情況下,Java虛擬機以平台相關的形式從本地的文件系統中加載類,比如在UNIX系統,虛擬機從CLASSPATH環境所定義的目錄加載類。
    然而,有些類並不是來自於文件;它們是從其它來源得到的,比如網絡,或者是由應用本身構建【動態代理】。定義類(defineClass )方法會將字節數組轉換為Class的實例,這個新定義類的實例可以由Class.newInstance創建。

    由類加載器創建的對象的方法和構造方法可能引用其它的類,為了確定被引用的類,Java虛擬機會調用最初創建類的類加載器的loadClass方法。

    二進制名稱:以字符串參數的形式向CalssLoader提供的任意一個類名,必須是一個二進制的名稱,包含以下四種情況

    • “java.lang.String” 正常類
    • “javax.swing.JSpinner$DefaultEditor” 內部類
    • “java.security.KeyStore\(Builder\)FileBuilder$1″ KeyStore的內部類Builder的內部類FileBuilder的第一個匿名內部類
    • “java.net.URLClassLoader$3$1” URLClassLoader類的第三個匿名內部類的第一個匿名內部類

    代碼一:

    public class Test12 {
        public static void main(String[] args) {
            String[] strings = new String[6];
            System.out.println(strings.getClass().getClassLoader());
            // 運行結果:null
    
            Test12[] test12s = new Test12[1];
            System.out.println(test12s.getClass().getClassLoader());
            // 運行結果:sun.misc.Launcher$AppClassLoader@18b4aac2
    
            int[] ints = new int[2];
            System.out.println(ints.getClass().getClassLoader());
            // 運行結果:null
        }
    }

    loadClass方法

    loadClass的源碼如下, loadClass方法加載擁有指定的二進制名稱的Class,默認按照如下順序尋找類:

    • 調用findLoadedClass(String)檢查這個類是否被加載
    • 調用父類加載器的loadClass方法,如果父類加載器為null,就會調用啟動類加載器
    • 調用findClass(String)方法尋找

    使用上述步驟如果類被找到且resolve為true,就會去調用resolveClass(Class)方法

    protected Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException
    {
      synchronized (getClassLoadingLock(name)) {
          // First, check if the class has already been loaded
          Class<?> c = findLoadedClass(name);
          if (c == null) {
              long t0 = System.nanoTime();
              try {
                  if (parent != null) {
                      c = parent.loadClass(name, false);
                  } else {
                      c = findBootstrapClassOrNull(name);
                  }
              } catch (ClassNotFoundException e) {
                  // ClassNotFoundException thrown if class not found
                  // from the non-null parent class loader
              }
    
              if (c == null) {
                  // If still not found, then invoke findClass in order
                  // to find the class.
                  long t1 = System.nanoTime();
                  c = findClass(name);
    
                  // this is the defining class loader; record the stats
                  sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                  sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                  sun.misc.PerfCounter.getFindClasses().increment();
              }
          }
          if (resolve) {
              resolveClass(c);
          }
          return c;
      }
    }

    findClass方法

    findClass的源碼如下,findClass尋找擁有指定二進制名稱的類,JVM鼓勵我們重寫此方法,需要自定義加載器遵循雙親委託機制,該方法會在檢查完父類加載器之後被loadClass方法調用,默認返回ClassNotFoundException異常。

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

    defineClass方法

    defineClass的源碼如下,defineClass方法將一個字節數組轉換為Class的實例。

    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

    自定義類加載器

    /**
     * 繼承了ClassLoader,這是一個自定義的類加載器
     * @author 夜的那種黑丶
     */
    public class ClassLoaderTest extends ClassLoader {
        public static void main(String[] args) throws Exception {
            ClassLoaderTest loader = new ClassLoaderTest("loader");
           Class<?> clazz = loader.loadClass("classloader.Test01");
            Object object = clazz.newInstance();
            System.out.println(object);
            System.out.println(object.getClass().getClassLoader());
        }
        //------------------------------以上為測試代碼---------------------------------
    
        /**
         * 類加載器名稱,標識作用
         */
        private String classLoaderName;
    
        /**
         * 從磁盤讀物字節碼文件的擴展名
         */
        private String fileExtension = ".class";
    
        /**
         * 創建一個類加載器對象,將系統類加載器當做該類加載器的父加載器
         * @param classLoaderName 類加載器名稱
         */
        private ClassLoaderTest(String classLoaderName) {
            // 將系統類加載器當做該類加載器的父加載器
            super();
            this.classLoaderName = classLoaderName;
        }
    
        /**
         * 創建一個類加載器對象,显示指定該類加載器的父加載器
         * 前提是需要有一個類加載器作為父加載器
         * @param parent 父加載器
         * @param classLoaderName 類加載器名稱
         */
        private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
            // 显示指定該類加載器的父加載器
            super(parent);
            this.classLoaderName = classLoaderName;
        }
    
        /**
         * 尋找擁有指定二進制名稱的類,重寫ClassLoader類的同名方法,需要自定義加載器遵循雙親委託機制
         * 該方法會在檢查完父類加載器之後被loadClass方法調用
         * 默認返回ClassNotFoundException異常
         * @param className 類名
         * @return Class的實例
         * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
         */
        @Override
        protected Class<?> findClass(String className) throws ClassNotFoundException {
            byte[] data = this.loadClassData(className);
            /*
             * 通過defineClass方法將字節數組轉換為Class
             * defineClass:將一個字節數組轉換為Class的實例,在使用這個Class之前必須要被解析
             */
            return this.defineClass(className, data, 0 , data.length);
        }
    
        /**
         * io操作,根據類名找到對應文件,返回class文件的二進制信息
         * @param className 類名
         * @return class文件的二進制信息
         * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
         */
        private byte[] loadClassData(String className) throws ClassNotFoundException {
            InputStream inputStream = null;
            byte[] data;
            ByteArrayOutputStream byteArrayOutputStream = null;
    
            try {
                this.classLoaderName = this.classLoaderName.replace(".", "/");
                inputStream = new FileInputStream(new File(className + this.fileExtension));
                byteArrayOutputStream = new ByteArrayOutputStream();
    
                int ch;
                while (-1 != (ch = inputStream.read())) {
                    byteArrayOutputStream.write(ch);
                }
    
                data = byteArrayOutputStream.toByteArray();
            } catch (Exception e) {
                throw new ClassNotFoundException();
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    if (byteArrayOutputStream != null) {
                        byteArrayOutputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return data;
        }
    }

    以上是一段自定義類加載器的代碼,我們執行這段代碼

    classloader.Test01@7f31245a
    sun.misc.Launcher$AppClassLoader@18b4aac2

    可以看見,這段代碼中進行類加載的類加載器還是系統類加載器(AppClassLoader)。這是因為jvm的雙親委託機製造成的,private ClassLoaderTest(String classLoaderName)將系統類加載器當做我們自定義類加載器的父加載器,jvm的雙親委託機制使自定義類加載器委託系統類加載器完成加載。

    改造以下代碼,添加一個path屬性用來指定類加載位置:

    public class ClassLoaderTest extends ClassLoader {
        public static void main(String[] args) throws Exception {
            ClassLoaderTest loader = new ClassLoaderTest("loader");
            loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
            Class<?> clazz = loader.loadClass("classloader.Test01");
            System.out.println("class:" + clazz);
    
            Object object = clazz.newInstance();
            System.out.println(object);
            System.out.println(object.getClass().getClassLoader());
        }
        //------------------------------以上為測試代碼---------------------------------
    
        /**
         * 從指定路徑加載
         */
        private String path;
    
        ......
        
        /**
         * io操作,根據類名找到對應文件,返回class文件的二進制信息
         * @param className 類名
         * @return class文件的二進制信息
         * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
         */
        private byte[] loadClassData(String className) throws ClassNotFoundException {
            InputStream inputStream = null;
            byte[] data;
            ByteArrayOutputStream byteArrayOutputStream = null;
    
            className = className.replace(".", "/");
    
            try {
                this.classLoaderName = this.classLoaderName.replace(".", "/");
                inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
                byteArrayOutputStream = new ByteArrayOutputStream();
    
                int ch;
                while (-1 != (ch = inputStream.read())) {
                    byteArrayOutputStream.write(ch);
                }
    
                data = byteArrayOutputStream.toByteArray();
            } catch (Exception e) {
                throw new ClassNotFoundException();
            } finally {
                try {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    if (byteArrayOutputStream != null) {
                        byteArrayOutputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return data;
        }
    
        public void setPath(String path) {
            this.path = path;
        }
    }

    運行一下

    class:class classloader.Test01
    classloader.Test01@7f31245a
    sun.misc.Launcher$AppClassLoader@18b4aac2

    修改一下測試代碼,並刪除工程下的Test01.class文件

    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
       loader.setPath("/home/fanxuan/桌面/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz);
    
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }

    運行一下

    class:class classloader.Test01
    classloader.Test01@135fbaa4
    classloader.ClassLoaderTest@7f31245a

    分析

    改造后的兩塊代碼,第一塊代碼中加載類的是系統類加載器AppClassLoader,第二塊代碼中加載類的是自定義類加載器ClassLoaderTest。是因為ClassLoaderTest會委託他的父加載器AppClassLoader加載class,第一塊代碼的path直接是工程下,AppClassLoader可以加載到,而第二塊代碼的path在桌面目錄下,所以AppClassLoader無法加載到,然後ClassLoaderTest自身嘗試加載並成功加載到。如果第二塊代碼工程目錄下的Test01.class文件沒有被刪除,那麼依然是AppClassLoader加載。

    再來測試一塊代碼

    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz.hashCode());
    
        Object object = clazz.newInstance();
        System.out.println(object.getClass().getClassLoader());
    
        ClassLoaderTest loader2 = new ClassLoaderTest("loader");
        loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class<?> clazz2 = loader2.loadClass("classloader.Test01");
        System.out.println("class:" + clazz2.hashCode());
    
        Object object2 = clazz2.newInstance();
        System.out.println(object2.getClass().getClassLoader());
    }

    結果顯而易見,類由系統類加載器加載,並且clazz和clazz2是相同的。

    class:2133927002
    sun.misc.Launcher$AppClassLoader@18b4aac2
    class:2133927002
    sun.misc.Launcher$AppClassLoader@18b4aac2

    在改造一下

    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/桌面/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz.hashCode());
    
        Object object = clazz.newInstance();
        System.out.println(object.getClass().getClassLoader());
    
        ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
        loader2.setPath("/home/fanxuan/桌面/");
        Class<?> clazz2 = loader2.loadClass("classloader.Test01");
        System.out.println("class:" + clazz2.hashCode());
    
        Object object2 = clazz2.newInstance();
        System.out.println(object2.getClass().getClassLoader());
    }

    運行結果

    class:325040804
    classloader.ClassLoaderTest@7f31245a
    class:621009875
    classloader.ClassLoaderTest@45ee12a7

    ClassLoaderTest是顯而易見,但是clazz和clazz2是不同的,這是因為類加載器的命名空間的原因。

    我們可以通過設置父類加載器來讓loader和loader2處於同一命名空間

    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/桌面/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz.hashCode());
    
        Object object = clazz.newInstance();
        System.out.println(object.getClass().getClassLoader());
    
        ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
        loader2.setPath("/home/fanxuan/桌面/");
        Class<?> clazz2 = loader2.loadClass("classloader.Test01");
        System.out.println("class:" + clazz2.hashCode());
    
        Object object2 = clazz2.newInstance();
        System.out.println(object2.getClass().getClassLoader());
    }

    運行結果

    class:325040804
    classloader.ClassLoaderTest@7f31245a
    class:325040804
    classloader.ClassLoaderTest@7f31245a

    擴展:命名空間

    • 每個類加載器都有自己的命名空間,命名空間由該加載器及所有的父加載器所加載的類組成
    • 在同一命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類
    • 在不同的命名空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • 多線程編程(3)——synchronized原理以及使用

    多線程編程(3)——synchronized原理以及使用

    一、對象頭

      通常在java中一個對象主要包含三部分:

    • 對象頭 主要包含GC的狀態、、類型、類的模板信息(地址)、synchronization狀態等,在後面介紹。

    • 實例數據:程序代碼中定義的各種類型的字段內容。

    • 對齊數據:對象的大小必須是 8 字節的整數倍,此項根據情況而定,若對象頭和實例數據大小正好是8的倍數,則不需要對齊數據,否則大小就是8的差數。

    先看下面的實例、程序的輸出以及解釋。

    /*需提前引入jar包
    <!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core 解析java對象布局 -->
            <dependency>
                <groupId>org.openjdk.jol</groupId>
                <artifactId>jol-core</artifactId>
                <version>0.9</version>
            </dependency>
    ​
    */
    //Java對象以8個字節對其,不夠則使用對其數據
    public class Student {
        private int id;       // 4字節
        private boolean sex;  // 1字節
        public Student(int id, boolean sex){
            this.id = id;
            this.sex = sex;
        }
    }
    public class Test01 {
        public static void main(String[] args) {
            Student stu = new Student(6, true);
            //計算對象hash,底層是C++實現,不需要java去獲取,如果此處不調用,則後面的hash值不會去計算
            System.out.println("hashcode: " + stu.hashCode());  
            System.out.println(ClassLayout.parseInstance(stu).toPrintable());
        }
    }
    /* output
    hashcode: 523429237
    com.thread.synchronizeDemo.Student object internals:
    OFFSET SIZE TYPE DESCRIPTION        VALUE
     0     4    (object header)    01 75 e5 32 (00000001 01110101 11100101 00110010) (853898497)
    4      4     (object header)    1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
    8      4     (object header)    43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
    12     4       int Student.id                                6
    16     1   boolean Student.sex                               true
    17     7           (loss due to the next object alignment)
    Instance size: 24 bytes
    Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
    ​
    備註:上述代碼在64位的機器上運行,此時
    對象頭占  (4+4+4)*8 = 96 位(bit)
    實例數據  (4+1)*8 = 40 位(bit)
    對齊數據  7*8 = 56 位(bit) 因為Java對象以8個字節對其的方式,需補7byte去對齊
    */

       下面主要陳述對對象頭的解釋,內容從hotspot官網摘抄下來的信息:

    object header

      Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object’s layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.

    mark word

      The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.

    klass pointer

      The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original objec

      由此可知,對象頭主要包含GC的狀態(用4位表示——表示範圍0-15,用來記錄GC年齡,這也就是為什麼對象在survivor中從from區到to區來迴轉換15次後轉入到老年代tenured區)、類型、類的模板信息(地址)、synchronization 狀態等,由兩個字組成mark word和klass pointer(類元素據信息地址,具體數據通常在堆的方法區中,即8字節,但有時候會有一些優化設置,會開啟指針壓縮,將代表klass pointer的8字節變成4字節大小,這也是為什麼在上述代碼中對象頭大小是(8+4)byte,而不是16byte。)。本節最主要介紹對象頭的mark word這部分。關於對象頭中每部分bit所代表的意義可以查看hotspot源碼中代碼的注,這段註釋是從openjdk中拷貝的。

    JVM和hotspot、openjdk的區別

    JVM是一種產品的規範定義,hotspot(Oracle公司)是對該規範實現的產品,還有遵循這些規範的其他產品,比如J9(IBM開發的一個高度模塊化的JVM)、Zing VM等。

    openjdk是一個hotspot項目的大部分源代碼(可以通過編譯后變成.exe文件),hotspot小部分代碼Oracle並未公布

    // openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\share\vm\oops\markOop.hpp
    /*
    Bit-format of an object header (most significant first, big endian layout below):
    ​
    32 bits:
    --------
            hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
            JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
            size:32 ------------------------------------------>| (CMS free block)
            PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
    ​
    64 bits:
    --------
    unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
    JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
    PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
    size:64 ----------------------------------------------------->| (CMS free block)
    ​
    */

    可以看到在32位機器和64位機器中,對象的布局的差異還是很大的,本文主要 敘述64位機器下的布局,其實兩者無非是位數不同而已,大同小異。在64位機器用64位(8byte)表示Mark Word,首先前25位(0-25)是未被使用,接下來31位表示hash值,然後是對象分代年齡大小,最後Synchronized的鎖信息,分為兩部分,共3bit,如下錶,鎖的嚴格性依次是鎖、偏向鎖、輕量級鎖、重量級鎖。

     

    關於鎖的一些解釋

    無鎖

      無鎖沒有對資源進行鎖定,所有的線程都能訪問並修改同一個資源,但同時只有一個線程能修改成功。

    偏向鎖

      引入偏向鎖是為了在無多線程競爭的情況下,一段同步代碼一直被一個線程所訪問因為輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令,當由另外的線程所訪問,偏向鎖就會升級為輕量級鎖。

    輕量級鎖 

      當鎖是偏向鎖的時候,被另外的線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,從而提高性能。輕量級鎖所適應的場景是線程交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖。

    重量級鎖

      依賴於操作系統Mutex Lock所實現的鎖,JDK中對Synchronized做的種種優化,其核心都是為了減少這種重量級鎖的使用。JDK1.6以後,為了減少獲得鎖和釋放鎖所帶來的性能消耗,提高性能,引入了“輕量級鎖”和“偏向鎖”。  

    GC

      這並不是鎖的狀態,而是GC標誌,等待GC回收。

    現在開始從程序層面分析前面程序的對象頭的布局信息,在此之前需要知道的是,在windows中對於數據的存儲採用的是小端存儲,所以要反過來讀

    大端模式——是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中,這樣的存儲模式有點兒類似於把數據當作字符串順序處理:地址由小向大增加,而數據從高位往低位放;這和我們的閱讀習慣一致。

    小端模式——是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中,這種存儲模式將地址的高低和數據位權有效地結合起來,高地址部分權值高,低地址部分權值低。 一般在網絡中用的大端;本地用的小端;

    運行程序如下,可以看到對應的hashcode值被打印出來:

    public static void main(String[] args) {
         Student stu = new Student(6, true);
        //Integer.toHexString()此方法返回的字符串表示的無符號整數參數所表示的值以十六進制
         System.out.println("hashcode: " + Integer.toHexString(stu.hashCode()));
         System.out.println(ClassLayout.parseInstance(stu).toPrintable());
    }
    /*
    hashcode: 1f32e575
    com.thread.synchronizeDemo.Student object internals:
     OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
          0     4           (object header)                           01 75 e5 32 (00000001 01110101 11100101 00110010) (853898497)
          4     4           (object header)                           1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
          8     4           (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
         12     4       int Student.id                                6
         16     1   boolean Student.sex                               true
         17     7           (loss due to the next object alignment)
    Instance size: 24 bytes
    Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

    //前8個字節反過來看,可以看出對象頭的hash是1f32e575,同時是無鎖的狀態00000001
    */

     二、Monitor

           可以把它理解為一個同步工具(數據結構),也可以描述為一種同步機制,通常被描述為一個對象。每個對象都存在着一個 monitor 與之關聯,對象與其 monitor 之間的關係有存在多種實現方式,如monitor可以與對象一起創建銷毀或當線程試圖獲取對象鎖時自動生成,但當一個 monitor 被某個線程持有后,它便處於鎖定狀態(每一個線程都有一個可用 monitor record 列表)[具體可以看參考資料5]。需要注意的是這種監視器鎖是發生在對象的內部鎖已經變成重量級鎖的時候。

    /*  openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\share\vm\runtime\ObjectMonitor.hpp
    // initialize the monitor, exception the semaphore, all other fields // are simple integers or pointers ObjectMonitor() { _header = NULL; _count = 0; //記錄個數 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; //處於wait狀態的線程,會被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //處於等待鎖block狀態的線程,會被加入到該列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; } */

      Monitor的實現主要藉助三個結構去完成多線程的併發操作——_owner、_WaitSet 、_EntryList。當多個線程同時訪問由synchronized修飾的對象、類或一段同步代碼時,首先會進入_EntryList 集合,如果某個線程取得了_owner的所有權,該線程就可以去執行,如果該線程調用了wait()方法,就會放棄_owner的所有權,進入等待狀態,等下一次喚醒。如下圖(圖片摘自參考資料5)。

     三、synchronized的用法

         synchronized修飾方法和修飾一個代碼塊類似,只是作用範圍不一樣,修飾代碼塊是大括號括起來的範圍,而修飾方法範圍是整個函數。其中synchronized(this) 與synchronized(class) 之間的區別有以下五點要注意:

        1、對於靜態方法,由於此時對象還未生成,所以只能採用類鎖;

        2、只要採用類鎖,就會攔截所有線程,只能讓一個線程訪問。

        3、對於對象鎖(this),如果是同一個實例,就會按順序訪問,但是如果是不同實例,就可以同時訪問。

       4、如果對象鎖跟訪問的對象沒有關係,那麼就會都同時訪問。

       5、當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。

    當然,Synchronized也可修飾一個靜態方法,而靜態方法是屬於類的而不屬於對象的,所以synchronized修飾的靜態方法鎖定的是這個類的所有對象。關於如下synchronized的用法,我們經常會碰到的案例:

    public class Thread5 implements Runnable {
        private static int count = 0;
        public synchronized static void add() {
            count++;
        }
        @Override
        public void run() {
            for (int i = 0; i < 1000000; i++) {
                synchronized (Thread5.class){
                    count++;
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            ExecutorService es = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 20; i++) {
                es.execute(new Thread5());
            }
            es.shutdown();
            es.awaitTermination(6, TimeUnit.SECONDS);
            System.out.println(count);
        }
    }
    /* 類鎖
      20000000
      */

    而一旦換成對象鎖,不同實例,就可以同時訪問。則會出錯:

    public void run() {
            for (int i = 0; i < 1000000; i++) {
                synchronized (this){
                    count++;
                }
            }
    }
    /* 對象鎖
     10746948
    */
    

    這是因為靜態變量並不屬於某個實例對象,而是屬於類所有,所以對某個實例加鎖,並不會改變count變量臟讀和臟寫的情況,還是造成結果不正確。

     

    參考資料

    1. 對象布局的各部分介紹——

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • [學習筆記] 在Eclipse中使用Hibernate,並創建第一個Demo工程,數據庫為Oracle XE

    [學習筆記] 在Eclipse中使用Hibernate,並創建第一個Demo工程,數據庫為Oracle XE

    前文參考:

    在Eclipse中使用Hibernate

    安裝 Hibernate Tools 插件

    https://tools.jboss.org/downloads/

    Add the following URL to your Eclipse 4.13 (2019-09) installation, via:

    Help > Install New Software… > Work with:

    http://download.jboss.org/jbosstools/photon/stable/updates/

    Then select the individual features that you want to install:

    點擊Next

    點擊Next
    同意相關協議,點擊Finish .

    則會開始下載安裝。

    視網絡速度,可能需要幾分鐘到十幾分鐘的時間才能完成安裝。

    最後會提示重啟Eclipse才能生效。

    在Eclipse中新建Hibernate應用

    File->New -> Java Project

    點擊Finish

    項目結構圖

    在Eclipse中新建用戶庫

    此時下面显示了已經建立的用戶庫列表

    我們要添加Hibernate的依賴庫,因此點擊用戶庫

    Hibernate_4.3.5_final

    選擇jar文件

    項目結構圖

    繼續配置Hibernate

    最後自動形成 如下的文件內容:[本例使用oracle數據庫]

    Oracle 11g xe 在windows安裝請看如下鏈接:

    hibernate.cfg.xml

     <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
        <session-factory>
            <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
            <property name="hibernate.connection.password">123456</property>
            <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:xe</property>
            <property name="hibernate.connection.username">test</property>
            <property name="hibernate.default_schema">test</property>
            <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
        </session-factory>
    </hibernate-configuration>

    再增加幾個屬性

    配置文件更新后的內容如下: 注意要去掉name屬性 更改 為

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                                             "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
     <session-factory >
      <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
      <property name="hibernate.connection.password">123456</property>
      <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:xe:orcl</property>
      <property name="hibernate.connection.username">test</property>
      <property name="hibernate.default_schema">test</property>
      <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
      <property name="hibernate.show_sql">true</property>
      <!-- 第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據 model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器后,表結構是不會被馬上建立起來的,是要等 應用第一次運行起來后才會。 -->
      <property name="hibernate.hbm2ddl.auto">update</property> 
      <property name="hibernate.format_sql">true</property>
      <property name="hibernate.default_entity_mode">pojo</property>
     </session-factory>
    </hibernate-configuration>

    繼續完善工程Hibernate_demo_001

    新建一個包:mytest001.demo

    在包mytest001.demo之下新建一個PO類: Emp

    package mytest001.demo;
    
    public class Emp {
        // 員工的標識屬性
        private Integer id;
        // 姓名
        private String name;
        // 年齡
        private Integer age;
        // 工資 (分)
        private Integer salary;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public Integer getSalary() {
            return salary;
        }
    
        public void setSalary(Integer salary) {
            this.salary = salary;
        }
    
        @Override
        public String toString() {
            return "Emp [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
        }
    }
    
    

    此刻此PO Emp.java 尚不具備持久化能力。下面為其添加註解。

    @Entity 註解聲明該類是一個Hibernate持久化類
    @Table 指定該類映射的表,對應的數據庫表名是T_EMP
    @Id 指定該類的標識屬性,映射到數據庫的主鍵列
    @GeneratedValue(strategy=GenerationType.SEQUENCE) 指定了主鍵生成策略,由於本文使用Oracle Database, 因此指定了使用 SEQUENCE

    在hibernate.cfg.xml中增加持久化映射類名

    增加一個新類:EmpManager,用於管理員工。

    package mytest001.demo;
    
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.Transaction;
    import org.hibernate.cfg.Configuration;
    import org.hibernate.service.Service;
    import org.hibernate.service.ServiceRegistry;
    import org.hibernate.service.ServiceRegistryBuilder;
    
    
    public class EmpManager {
    
        public static void main(String[] args)  throws Exception  {
             
        //實例化配置
        Configuration configuration  = new Configuration()
                //不帶參數則默認加載hibernate.cfg.xml
                .configure();
        
        ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
                .applySettings(configuration.getProperties()).build();
        SessionFactory sFactory = configuration.buildSessionFactory(serviceRegistry);
        
        //創建session 
        Session session = sFactory.openSession();
        
        //開始事務
        Transaction tx = session.beginTransaction();
        //創建員工對象
        Emp emp = new Emp();
        // 設置員工信息
        emp.setAge(28);
        emp.setName("scott");
        emp.setSalary(10000);
        session.save(emp);
        // 提交事務
        tx.commit();
        session.close();
        sFactory.close();
                
        
        }
    
    }

    最後配置文件的內容:hibernate.cfg.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                                             "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
     <session-factory >
      <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
      <property name="hibernate.connection.password">123456</property>
      <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:xe</property>
      <property name="hibernate.connection.username">test</property>
      <property name="hibernate.default_schema">test</property>
      <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
      <property name="hibernate.show_sql">true</property>
      <!-- 第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據 model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器后,表結構是不會被馬上建立起來的,是要等 應用第一次運行起來后才會。 -->
      <property name="hibernate.hbm2ddl.auto">update</property> 
      <property name="hibernate.format_sql">true</property>
      <property name="hibernate.default_entity_mode">pojo</property>
      <mapping class="mytest001.demo.Emp"/>
     </session-factory>
    </hibernate-configuration>
    

    Emp.java

    package mytest001.demo;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Entity
    @Table(name="T_EMP")
    public class Emp {
        // 員工的標識屬性
        @Id
        @GeneratedValue(strategy=GenerationType.SEQUENCE)
        private Integer id;
        // 姓名
        private String name;
        // 年齡
        private Integer age;
        // 工資 (分)
        private Integer salary;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public Integer getSalary() {
            return salary;
        }
    
        public void setSalary(Integer salary) {
            this.salary = salary;
        }
    
        @Override
        public String toString() {
            return "Emp [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
        }
    }
    

    最後的工程結構如下:

    運行EmpManger

    十一月 23, 2019 8:50:47 上午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
    INFO: HCANN000001: Hibernate Commons Annotations {4.0.4.Final}
    十一月 23, 2019 8:50:47 上午 org.hibernate.Version logVersion
    INFO: HHH000412: Hibernate Core {4.3.5.Final}
    十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Environment <clinit>
    INFO: HHH000206: hibernate.properties not found
    十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Environment buildBytecodeProvider
    INFO: HHH000021: Bytecode provider name : javassist
    十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration configure
    INFO: HHH000043: Configuring from resource: /hibernate.cfg.xml
    十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration getConfigurationInputStream
    INFO: HHH000040: Configuration resource: /hibernate.cfg.xml
    十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration doConfigure
    INFO: HHH000041: Configured SessionFactory: null
    十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
    WARN: HHH000402: Using Hibernate built-in connection pool (not for production use!)
    十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
    INFO: HHH000401: using driver [oracle.jdbc.driver.OracleDriver] at URL [jdbc:oracle:thin:@localhost:1521:xe]
    十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
    INFO: HHH000046: Connection properties: {user=test, password=****}
    十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
    INFO: HHH000006: Autocommit mode: false
    十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
    INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
    十一月 23, 2019 8:50:48 上午 org.hibernate.dialect.Dialect <init>
    INFO: HHH000400: Using dialect: org.hibernate.dialect.Oracle10gDialect
    十一月 23, 2019 8:50:48 上午 org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
    INFO: HHH000399: Using default transaction strategy (direct JDBC transactions)
    十一月 23, 2019 8:50:48 上午 org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
    INFO: HHH000397: Using ASTQueryTranslatorFactory
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
    INFO: HHH000228: Running hbm2ddl schema update
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
    INFO: HHH000102: Fetching database metadata
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
    INFO: HHH000396: Updating schema
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
    INFO: HHH000262: Table not found: T_EMP
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
    INFO: HHH000262: Table not found: T_EMP
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
    INFO: HHH000262: Table not found: T_EMP
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
    INFO: HHH000232: Schema update complete
    Hibernate: 
        select
            test.hibernate_sequence.nextval 
        from
            dual
    Hibernate: 
        insert 
        into
            test.T_EMP
            (age, name, salary, id) 
        values
            (?, ?, ?, ?)
    十一月 23, 2019 8:50:48 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
    INFO: HHH000030: Cleaning up connection pool [jdbc:oracle:thin:@localhost:1521:xe]
    

    到數據庫中查詢表:(這個表會被自動創建)
    select * from t_emp;

    如果再次運行會增加新的記錄。

    至此,本文完成。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • Spring Boot2 系列教程(二十六)Spring Boot 整合 Redis

    Spring Boot2 系列教程(二十六)Spring Boot 整合 Redis

    在 Redis 出現之前,我們的緩存框架各種各樣,有了 Redis ,緩存方案基本上都統一了,關於 Redis,松哥之前有一個系列教程,尚不了解 Redis 的小夥伴可以參考這個教程:

    使用 Java 操作 Redis 的方案很多,Jedis 是目前較為流行的一種方案,除了 Jedis ,還有很多其他解決方案,如下:

    除了這些方案之外,還有一個使用也相當多的方案,就是 Spring Data Redis。

    在傳統的 SSM 中,需要開發者自己來配置 Spring Data Redis ,這個配置比較繁瑣,主要配置 3 個東西:連接池、連接器信息以及 key 和 value 的序列化方案。

    在 Spring Boot 中,默認集成的 Redis 就是 Spring Data Redis,默認底層的連接池使用了 lettuce ,開發者可以自行修改為自己的熟悉的,例如 Jedis。

    Spring Data Redis 針對 Redis 提供了非常方便的操作模板 RedisTemplate 。這是 Spring Data 擅長的事情,那麼接下來我們就來看看 Spring Boot 中 Spring Data Redis 的具體用法。

    方案一:Spring Data Redis

    創建工程

    創建工程,引入 Redis 依賴:

    創建成功后,還需要手動引入 commos-pool2 的依賴,因此最終完整的 pom.xml 依賴如下:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
    </dependencies>

    這裏主要就是引入了 Spring Data Redis + 連接池。

    配置 Redis 信息

    接下來配置 Redis 的信息,信息包含兩方面,一方面是 Redis 的基本信息,另一方面則是連接池信息:

    spring.redis.database=0
    spring.redis.password=123
    spring.redis.port=6379
    spring.redis.host=192.168.66.128
    spring.redis.lettuce.pool.min-idle=5
    spring.redis.lettuce.pool.max-idle=10
    spring.redis.lettuce.pool.max-active=8
    spring.redis.lettuce.pool.max-wait=1ms
    spring.redis.lettuce.shutdown-timeout=100ms

    自動配置

    當開發者在項目中引入了 Spring Data Redis ,並且配置了 Redis 的基本信息,此時,自動化配置就會生效。

    我們從 Spring Boot 中 Redis 的自動化配置類中就可以看出端倪:

    @Configuration
    @ConditionalOnClass(RedisOperations.class)
    @EnableConfigurationProperties(RedisProperties.class)
    @Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
    public class RedisAutoConfiguration {
        @Bean
        @ConditionalOnMissingBean(name = "redisTemplate")
        public RedisTemplate<Object, Object> redisTemplate(
                        RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
                RedisTemplate<Object, Object> template = new RedisTemplate<>();
                template.setConnectionFactory(redisConnectionFactory);
                return template;
        }
        @Bean
        @ConditionalOnMissingBean
        public StringRedisTemplate stringRedisTemplate(
                        RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
                StringRedisTemplate template = new StringRedisTemplate();
                template.setConnectionFactory(redisConnectionFactory);
                return template;
        }
    }

    這個自動化配置類很好理解:

    1. 首先標記這個是一個配置類,同時該配置在 RedisOperations 存在的情況下才會生效(即項目中引入了 Spring Data Redis)
    2. 然後導入在 application.properties 中配置的屬性
    3. 然後再導入連接池信息(如果存在的話)
    4. 最後,提供了兩個 Bean ,RedisTemplate 和 StringRedisTemplate ,其中 StringRedisTemplate 是 RedisTemplate 的子類,兩個的方法基本一致,不同之處主要體現在操作的數據類型不同,RedisTemplate 中的兩個泛型都是 Object ,意味者存儲的 key 和 value 都可以是一個對象,而 StringRedisTemplate 的 兩個泛型都是 String ,意味者 StringRedisTemplate 的 key 和 value 都只能是字符串。如果開發者沒有提供相關的 Bean ,這兩個配置就會生效,否則不會生效。

    使用

    接下來,可以直接在 Service 中注入 StringRedisTemplate 或者 RedisTemplate 來使用:

    @Service
    public class HelloService {
        @Autowired
        RedisTemplate redisTemplate;
        public void hello() {
            ValueOperations ops = redisTemplate.opsForValue();
            ops.set("k1", "v1");
            Object k1 = ops.get("k1");
            System.out.println(k1);
        }
    }

    Redis 中的數據操作,大體上來說,可以分為兩種:

    1. 針對 key 的操作,相關的方法就在 RedisTemplate 中
    2. 針對具體數據類型的操作,相關的方法需要首先獲取對應的數據類型,獲取相應數據類型的操作方法是 opsForXXX

    調用該方法就可以將數據存儲到 Redis 中去了,如下:

    k1 前面的字符是由於使用了 RedisTemplate 導致的,RedisTemplate 對 key 進行序列化之後的結果。

    RedisTemplate 中,key 默認的序列化方案是 JdkSerializationRedisSerializer 。

    而在 StringRedisTemplate 中,key 默認的序列化方案是 StringRedisSerializer ,因此,如果使用 StringRedisTemplate ,默認情況下 key 前面不會有前綴。

    不過開發者也可以自行修改 RedisTemplate 中的序列化方案,如下:

    @Service
    public class HelloService {
        @Autowired
        RedisTemplate redisTemplate;
        public void hello() {
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            ValueOperations ops = redisTemplate.opsForValue();
            ops.set("k1", "v1");
            Object k1 = ops.get("k1");
            System.out.println(k1);
        }
    }

    當然也可以直接使用 StringRedisTemplate:

    @Service
    public class HelloService {
        @Autowired
        StringRedisTemplate stringRedisTemplate;
        public void hello2() {
            ValueOperations ops = stringRedisTemplate.opsForValue();
            ops.set("k2", "v2");
            Object k1 = ops.get("k2");
            System.out.println(k1);
        }
    }

    另外需要注意 ,Spring Boot 的自動化配置,只能配置單機的 Redis ,如果是 Redis 集群,則所有的東西都需要自己手動配置,關於如何操作 Redis 集群,松哥以後再來和大家分享。

    方案二:Spring Cache

    通過 Spring Cache 的形式來操作 Redis,Spring Cache 統一了緩存江湖的門面,這種方案,松哥之前有過一篇專門的文章介紹,小夥伴可以移步這裏:。

    方案三:回歸原始時代

    第三種方案,就是直接使用 Jedis 或者 其他的客戶端工具來操作 Redis ,這種方案在 Spring Boot 中也是支持的,雖然操作麻煩,但是支持,這種操作松哥之前也有介紹的文章,因此這裏就不再贅述了,可以參考 。

    總結

    Spring Boot 中,Redis 的操作,這裏松哥給大家總結了三種方案,實際上前兩個使用廣泛一些,直接使用 Jedis 還是比較少,基本上 Spring Boot 中沒見過有人直接這麼搞。

    好了,本文就說到這裏,有問題歡迎留言討論。

    相關案例已經上傳到 GitHub,歡迎小夥伴們們下載:

    掃碼關注松哥,公眾號後台回復 2TB,獲取松哥獨家 超2TB 免費 Java 學習乾貨

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益