java日常知識點積累

草帽發表於2018-01-10

前言

  • 知識點整理來源於網路,個人只是單純備份記錄,如有侵權請聯絡本人處理(lvzhi1988@126.com),持續整理中。。。

1,java多執行緒與synchronized

  • java型別中的普通非static方法

示例程式碼:

package com.lvzhi;

/**
 * Created by lvzhi on 2017/9/3
 */
public class MyThread {
    private int num = 0;

    public synchronized void print(String args) throws InterruptedException {
        if (args.equals("a")) {
            System.out.println("I'am a ");
            num = 100;
            Thread.sleep(1000L);
        } else {
            System.out.println("I'am b ");
            num = 200;
            Thread.sleep(1000L);
        }
        System.out.println("Over");
    }

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    myThread1.print("a");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    myThread2.print("b");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();

    }
}

複製程式碼

該段程式碼輸出的結果是thread1和thread2的亂序結果,因為synchronized是在非static方法,那麼對於執行緒來說就是myThread1和myThread2是相互獨立的,類似於上邊的程式碼跟沒加synchronized效果是一樣的。 結果如下: I'am a I'am b Over Over


*java型別中static方法

示例程式碼:

package com.lvzhi;

/**
 * Created by lvzhi on 2017/9/3
 */
public class MyThread {
    private static int num = 0;

    public static synchronized void print(String args) throws InterruptedException {
        if (args.equals("a")) {
            System.out.println("I'am a ");
            num = 100;
            Thread.sleep(1000L);
        } else {
            System.out.println("I'am b ");
            num = 200;
            Thread.sleep(1000L);
        }
        System.out.println("Over");
    }

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    myThread1.print("a");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    myThread2.print("b");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();

    }
}

複製程式碼

該段程式碼輸出的結果是thread1和thread2的有序結果,因為synchronized是在static方法上,static方法特性自己去百度,所以結果是有序的。 結果如下: I'am a Over I'am b Over

個人總結:synchronized加在static鎖定的就是class類,類級別的鎖。


2,java同一個類中不同方法的synchronized

  • 結論:同一個類中的所有synchronized方法,如果有一個synchronized方法已經被使用,那麼該類中其他所有的synchronized方法都不能被使用,只能等第一個synchronized方法被釋放,其他的synchronized方法才能使用;但是,其他的非synchronized方法可以被呼叫。結果就是synchronized鎖定的class物件,第一個執行緒呼叫某個synchronized方法時,該class就被鎖定了。 結合1,如果又new出來一個該類,那麼是不衝突的。

3,髒讀

  • 在oracle中,有一個概念叫“undo”,就是每次修改或者刪除一條資料的時候,會把這個資料放置在undo中,如果需要回滾就去undo中找。 所以,如果在9點這一刻發起一個查詢請求,檢索一條資料,這個請求需要執行10分鐘才能找到想要的資料,但是在9點5分的時候,有另個請求修改了想要查詢的資料,那麼在9點10分的時候,檢視到的還是老資料,就是因為“undo”。 在mysql中應該也是一樣的。查詢發出的那一刻,資料是什麼就是什麼。

4, 類鎖,物件鎖,方法鎖

  • 1 ,其實只有兩種鎖,方法鎖和物件鎖是同一個東西,所以只有物件鎖和類鎖。
  • 2,synchronized 加到 static 方法前面是給class 加鎖,即類鎖;而synchronized 加到非靜態方法前面是給物件上鎖。
  • 3, 如果多執行緒同時訪問同一類的 類鎖(synchronized 修飾的靜態方法)以及物件鎖(synchronized 修飾的非靜態方法)這兩個方法執行是非同步的,原因:類鎖和物件鎖是2中不同的鎖。
  • 4, 類鎖對該類的所有物件都能起作用,而物件鎖不能。

5,sleep & wait | notify | notifyAll

一、sleep & wait

  1. 兩者來自不同的類(分別是Thread和Object) 2.sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他執行緒可以使用同步控制塊或方法 3.wiat只能在同步控制方法或者同步控制塊使用,而sleep可以在任何地方使用 4.sleep必須捕獲異常,而wait不需要 二、wait和notify為什麼會封裝在Object類中,而不是像sleep方法在Thread中? wait和notify的本質是基於條件物件,而且只能由已經獲得鎖的執行緒呼叫。java的每個Object都有一個隱式鎖,這個隱式鎖關聯一個Condition條件物件, 執行緒拿到這個隱式所(比如進入synchronized程式碼區域),就可以呼叫wait,語義是Condition條件物件上等待,其他的執行緒可以在這個Condition條件物件上等待 , 等滿足條件之後,就可以呼叫notify或者notifyAll來喚醒所有在此條件物件上等待的執行緒。 三、死鎖(產生死鎖的必要條件) 互斥條件:一個資源每次只能被一個程式使用 請求與保持條件:一個程式因請求資源而阻塞時,對已獲得的資源保持不放。 不剝奪條件:程式已獲得的資源,在未使用完之前,不能強制剝奪 迴圈等待條件:若干程式之間形成一種頭尾相接的迴圈資源關係。 這四個條件時死鎖的必要條件,只有系統發生死鎖,這些條件必然成立,只要上述條件之一不滿足,就不發生死鎖。 四、常識 1.在java中所有執行緒都是同時啟動的,至於什麼時候,哪個先執行,完全看誰先得到CPU的資源。 2.在java中,每次程式執行至少啟動2個執行緒。一個是main執行緒,一個是垃圾回收執行緒。因為每當使用java命令執行一個類的時候,實際上啟動了一個jvm, 每個jvm其實就是在作業系統中啟動了一個執行緒 3.理解java編譯器的執行緒處理和jvm。有助於程式設計高效、效能更好的java程式碼。 五、java中wait/notify機制 notify():喚醒一個處於等待狀態的執行緒,注意的是在呼叫此方法的時候,並不能確切的喚醒某一個等待狀態的執行緒,而是由JVM確定喚醒哪個執行緒, 而且不是按優先順序。notify()方法和wait()方法的基本思想是給方法或程式碼塊提供一種相互通訊的方式,而這些方法或者程式碼塊同步於某個特定物件。 程式碼塊可以呼叫wait()方法來將自身的操作掛起,直到同一個物件上的其他同步方法或同步程式碼塊以某種方式將其改變,並呼叫notfiy()方法來通知此程式碼塊改變已經完成。 一個執行緒一般會因為它所同步的物件的某個屬性沒有設定,或者某個條件沒有滿足而呼叫wait()方法,這些由另一個執行緒的動作決定。 最簡單的情況可能是資源因為正被另一個執行緒修改而繁忙,還有其他的可能情況。 通 常,多執行緒之間需要協調工作。例如,瀏覽器的一個顯示圖片的執行緒displayThread想要執行顯示圖片的任務,必須等待下載線 程 downloadThread將該圖片下載完畢。如果圖片還沒有下載完,displayThread可以暫停,當downloadThread完成了 任務 後,再通知displayThread“圖片準備完畢,可以顯示了”,這時,displayThread繼續執行。 以上邏輯簡單的說就是:如果條件不滿足,則等待。當條件滿足時,等待該條件的執行緒將被喚醒。在Java中,這個機制的實現依賴於wait/notify。等待機制與鎖機制是密切關聯的。例如: synchronized(obj) {while(!condition) {obj.wait();}obj.doSomething();} 當執行緒A獲得了obj鎖後,發現條件condition不滿足,無法繼續下一處理,於是執行緒A就wait()。 在另一執行緒B中,如果B更改了某些條件,使得執行緒A的condition條件滿足了,就可以喚醒執行緒A: synchronized(obj) {condition = true;obj.notify();} 需要注意的概念是: ◆呼叫obj的wait(), notify()方法前,必須獲得obj鎖,也就是必須寫在synchronized(obj) {...} 程式碼段內。 ◆呼叫obj.wait()後,執行緒A就釋放了obj的鎖,否則執行緒B無法獲得obj鎖,也就無法在synchronized(obj) {...} 程式碼段內喚醒A。 ◆當obj.wait()方法返回後,執行緒A需要再次獲得obj鎖,才能繼續執行。 ◆如果A1,A2,A3都在obj.wait(),則B呼叫obj.notify()只能喚醒A1,A2,A3中的一個(具體哪一個由JVM決定)。 ◆obj.notifyAll()則能全部喚醒A1,A2,A3,但是要繼續執行obj.wait()的下一條語句,必須獲得obj鎖,因此,A1,A2,A3只有一個有機會獲得鎖繼續執行,例如A1,其餘的需要等待A1釋放obj鎖之後才能繼續執行。 ◆當B呼叫obj.notify/notifyAll的時候,B正持有obj鎖,因此,A1,A2,A3雖被喚醒,但是仍無法獲得obj鎖。直到B退出synchronized塊,釋放obj鎖後,A1,A2,A3中的一個才有機會獲得鎖繼續執行。 七、join join方法的功能就是使非同步執行的執行緒變成同步執行。也就是說,當呼叫執行緒例項的start方法後,這個方法會立即返回,如果在呼叫start方法後後需要使用一個由這個執行緒計算得到的值,就必須使用join方法。如果不使用join方法,就不能保證當執行到start方法後面的某條語句時,這個執行緒一定會執行完。而使用join方法後,直到這個執行緒退出,程式才會往下執行。例如:你準備洗澡,需要準備的步驟,準備好衣服,沐浴的東西及燒水這些事情,由於燒水耗時太長,如果也放在主執行緒之中,就很浪費資源,所以如果我們另開執行緒去處 理,就會達到很好效果,於是乎在準備好衣服,沐浴的東西之前就去開子執行緒燒水,燒水的過程中主執行緒準備好衣服,沐浴的東西,此時就等待水燒好,然後方可痛 快的洗澡了!!

6,ConcurrentHashMap

有時間讀讀原始碼

7,Queue

  • 佇列是一種資料結構.它有兩個基本操作:在佇列尾部加人一個元素,和從佇列頭部移除一個元素就是說,佇列以一種先進先出的方式管理資料,如果你試圖向一個 已經滿了的阻塞佇列中新增一個元素或者是從一個空的阻塞佇列中移除一個元索,將導致執行緒阻塞.在多執行緒進行合作時,阻塞佇列是很有用的工具。
  • add 增加一個元索 如果佇列已滿,則丟擲一個IIIegaISlabEepeplian異常 remove 移除並返回佇列頭部的元素 如果佇列為空,則丟擲一個NoSuchElementException異常 element 返回佇列頭部的元素 如果佇列為空,則丟擲一個NoSuchElementException異常 offer 新增一個元素並返回true 如果佇列已滿,則返回false poll 移除並返問佇列頭部的元素 如果佇列為空,則返回null peek 返回佇列頭部的元素 如果佇列為空,則返回null put 新增一個元素 如果佇列滿,則阻塞 take 移除並返回佇列頭部的元素 如果佇列為空,則阻塞

8,CopyOnWriteArrayList

  • 讀的時候不需要加鎖,如果讀的時候有多個執行緒正在向ArrayList新增資料,讀還是會讀到舊的資料,因為寫的時候不會鎖住舊的ArrayList。
  • CopyOnWrite併發容器用於讀多寫少的併發場景。比如白名單,黑名單,商品類目的訪問和更新場景
  • CopyOnWrite的缺點 CopyOnWrite容器有很多優點,但是同時也存在兩個問題,即記憶體佔用問題和資料一致性問題。所以在開發的時候需要注意一下。 記憶體佔用問題: 因為CopyOnWrite的寫時複製機制,所以在進行寫操作的時候,記憶體裡會同時駐紮兩個物件的記憶體,舊的物件和新寫入的物件(注意:在複製的時候只是複製容器裡的引用,只是在寫的時候會建立新物件新增到新容器裡,而舊容器的物件還在使用,所以有兩份物件記憶體)。如果這些物件佔用的記憶體比較大,比如說200M左右,那麼再寫入100M資料進去,記憶體就會佔用300M,那麼這個時候很有可能造成頻繁的Yong GC和Full GC。之前我們系統中使用了一個服務由於每晚使用CopyOnWrite機制更新大物件,造成了每晚15秒的Full GC,應用響應時間也隨之變長。 針對記憶體佔用問題,可以通過壓縮容器中的元素的方法來減少大物件的記憶體消耗,比如,如果元素全是10進位制的數字,可以考慮把它壓縮成36進位制或64進位制。或者不使用CopyOnWrite容器,而使用其他的併發容器,如ConcurrentHashMap。 資料一致性問題: CopyOnWrite容器只能保證資料的最終一致性,不能保證資料的實時一致性。所以如果你希望寫入的的資料,馬上能讀到,請不要使用CopyOnWrite容器。

9,阻塞和非阻塞佇列

  • 阻塞佇列與普通佇列的區別在於,當佇列是空的時,從佇列中獲取元素的操作將會被阻塞,或者當佇列是滿時,往佇列裡新增元素的操作會被阻塞。試圖從空的阻塞佇列中獲取元素的執行緒將會被阻塞,直到其他的執行緒往空的佇列插入新的元素。同樣,試圖往已滿的阻塞佇列中新增新元素的執行緒同樣也會被阻塞,直到其他的執行緒使佇列重新變得空閒起來,如從佇列中移除一個或者多個元素,或者完全清空佇列.
  • 1.ArrayDeque, (陣列雙端佇列) 2.PriorityQueue, (優先順序佇列) 3.ConcurrentLinkedQueue, (基於連結串列的併發佇列) 4.DelayQueue, (延期阻塞佇列)(阻塞佇列實現了BlockingQueue介面) 5.ArrayBlockingQueue, (基於陣列的併發阻塞佇列) 6.LinkedBlockingQueue, (基於連結串列的FIFO阻塞佇列) 7.LinkedBlockingDeque, (基於連結串列的FIFO雙端阻塞佇列) 8.PriorityBlockingQueue, (帶優先順序的無界阻塞佇列) 9.SynchronousQueue (併發同步阻塞佇列)
  • ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列。 LinkedBlockingQueue :一個由連結串列結構組成的有界阻塞佇列。 PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。 DelayQueue:一個使用優先順序佇列實現的無界阻塞佇列。 SynchronousQueue:一個不儲存元素的阻塞佇列。 LinkedTransferQueue:一個由連結串列結構組成的無界阻塞佇列。 LinkedBlockingDeque:一個由連結串列結構組成的雙向阻塞佇列。
  • 方法\處理方式 丟擲異常 返回特殊值 一直阻塞 超時退出 插入方法 add(e) offer(e) put(e) offer(e,time,unit) 移除方法 remove() poll() take() poll(time,unit) 檢查方法 element() peek() 不可用 不可用
  • 丟擲異常:是指當阻塞佇列滿時候,再往佇列裡插入元素,會丟擲IllegalStateException(“Queue full”)異常。當佇列為空時,從佇列裡獲取元素時會丟擲NoSuchElementException異常 。 返回特殊值:插入方法會返回是否成功,成功則返回true。移除方法,則是從佇列裡拿出一個元素,如果沒有則返回null 一直阻塞:當阻塞佇列滿時,如果生產者執行緒往佇列裡put元素,佇列會一直阻塞生產者執行緒,直到拿到資料,或者響應中斷退出。當佇列空時,消費者執行緒試圖從佇列裡take元素,佇列也會阻塞消費者執行緒,直到佇列可用。 超時退出:當阻塞佇列滿時,佇列會阻塞生產者執行緒一段時間,如果超過一定的時間,生產者執行緒就會退出。 BlockingQueue
  • 獲取元素的時候等待佇列裡有元素,否則阻塞 儲存元素的時候等待佇列裡有空間,否則阻塞 用來簡化生產者消費者在多執行緒環境下的開發
  • ArrayBlockingQueue FIFO、陣列實現 有界阻塞佇列,一旦指定了佇列的長度,則佇列的大小不能被改變 在生產者消費者例子中,如果生產者生產實體放入佇列超過了佇列的長度,則在offer(或者put,add)的時候會被阻塞,直到佇列的實體數量< 佇列的初始size為止。不過可以設定超時時間,超時後佇列還未空出位置,則offer失敗。 如果消費者發現佇列裡沒有可被消費的實體時也會被阻塞,直到有實體被生產出來放入佇列位置,不過可以設定等待的超時時間,超過時間後會返回null
  • DelayQueue 有界阻塞延時佇列,當佇列裡的元素延時期未到是,通過take方法不能獲取,會被阻塞,直到有元素延時到期為止 如: 1.obj 5s 延時到期 2.obj 6s 延時到期 3.obj 9s 延時到期 那麼在take的時候,需要等待5秒鐘才能獲取第一個obj,再過1s後可以獲取第二個obj,再過3s後可以獲得第三個obj 這個佇列可以用來處理session過期失效的場景,比如session在建立的時候設定延時到期時間為30分鐘,放入延時佇列裡,然後通過一個執行緒來獲取這個佇列元素,只要能被獲取到的,表示已經是過期的session,被獲取的session可以肯定超過30分鐘了,這時對session進行失效。
  • LinkedBlockingQueue FIFO、Node連結串列結構 可以通過構造方法設定capacity來使得阻塞佇列是有界的,也可以不設定,則為無界佇列 其他功能類似ArrayBlockingQueue
  • PriorityBlockingQueue 無界限佇列,相當於PriorityQueue + BlockingQueue 插入的物件必須是可比較的,或者通過構造方法實現插入物件的比較器Comparator<? super E> 佇列裡的元素按Comparator<? super E> comparator比較結果排序,PriorityBlockingQueue可以用來處理一些有優先順序的事物。比如簡訊傳送優先順序佇列,佇列裡已經有某企業的100000條簡訊,這時候又來了一個100條緊急簡訊,優先順序別比較高,可以通過PriorityBlockingQueue來輕鬆實現這樣的功能。這樣這個100條可以被優先傳送
  • SynchronousQueue 無內部容量的阻塞佇列,put必須等待take,同樣take必須等待put。比較適合兩個執行緒間的資料傳遞。非同步轉同步的場景不太適用,因為對於非同步執行緒來說在處理完事務後進行put,但是必須等待put的值被取走。

10,Future

這個配合callable使用,其實就是多執行緒有返回值,so easy !

11,master-worker

Master-Worker是常用的平行計算模式。它的核心思想是系統由兩類程式協作工作:Master程式和Worker程式。Master負責接收和分配任務,Worker負責處理子任務。當各個Worker子程式處理完成後,會將結果返回給Master,由Master作歸納總結。其好處就是能將一個大任務分解成若干個小任務,並行執行,從而提高系統的吞吐量。處理過程如下圖所示:

master-worker工作模式圖
Master-Worker模式是一種將序列任務並行化的方案,被分解的子任務在系統中可以被並行處理,同時,如果有需要,Master程式不需要等待所有子任務都完成計算,就可以根據已有的部分結果集計算最終結果集。

12,Semaphore訊號量

Semaphore當前在多執行緒環境下被擴放使用,作業系統的訊號量是個很重要的概念,在程式控制方面都有應用。Java 併發庫 的Semaphore 可以很輕鬆完成訊號量控制,Semaphore可以控制某個資源可被同時訪問的個數,通過 acquire() 獲取一個許可,如果沒有就等待,而 release() 釋放一個許可。比如在Windows下可以設定共享檔案的最大客戶端訪問個數。 Semaphore實現的功能就類似廁所有5個坑,假如有10個人要上廁所,那麼同時只能有多少個人去上廁所呢?同時只能有5個人能夠佔用,當5個人中 的任何一個人讓開後,其中等待的另外5個人中又有一個人可以佔用了。另外等待的5個人中可以是隨機獲得優先機會,也可以是按照先來後到的順序獲得機會,這取決於構造Semaphore物件時傳入的引數選項。單個訊號量的Semaphore物件可以實現互斥鎖的功能,並且可以是由一個執行緒獲得了“鎖”,再由另一個執行緒釋放“鎖”,這可應用於死鎖恢復的一些場合。 ps:有個重要問題,該段程式碼在main方法中執行,可以達到預期的效果,如果用junit的方法來測試,其預期的結果差很遠。

public class SemaphoreTest {
    public static void main(String[] args) {
        // 執行緒池
        ExecutorService exec = Executors.newCachedThreadPool();
        // 只能5個執行緒同時訪問
        final Semaphore semp = new Semaphore(5);
        // 模擬20個客戶端訪問
        for (int index = 0; index < 50; index++) {
            final int NO = index;
            Runnable run = new Runnable() {
                public void run() {
                    try {
                        // 獲取許可
                        semp.acquire();
                        System.out.println("Accessing: " + NO);
                        Thread.sleep((long) (Math.random() * 10000));
                        // 訪問完後,釋放
                        semp.release();
                        //availablePermits()指的是當前訊號燈庫中有多少個可以被使用
                        System.out.println("-----------------" + semp.availablePermits());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            exec.execute(run);
        }
        // 退出執行緒池
        exec.shutdown();
    }
複製程式碼

13,condition

  1. Condition可以替代傳統的執行緒間通訊,用await()替代wait,用signal替代notify(),用signalAll()替代notifyAll()。因為Object下面的wait/notify/notifyAll方法都是final的,所以名稱上全都發生了改變。傳統執行緒通訊方式,condition都能實現。
  2. 注意:condition()是被繫結到Lock上面的,要建立一個Lock的conditon,需要用newCondition 。現在知道了,synchronized和notidy/wait/notifyAll結合使用, lock和condition的await/signal/signalAll結合使用。
  3. condition的強大之處,它可以為多個執行緒之間建立不同的condition。
public class ConditionTest {
    private static Lock lock = new ReentrantLock();
    private static final Condition firstCondition = lock.newCondition();
    private static final  Condition secondCondition = lock.newCondition();
    //以上的兩個condition是不一樣的,不能用secondCondition去喚醒firstCondition
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("wait");
                    firstCondition.await();
                    System.out.println("over");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("enter");
                    Thread.sleep(10000);
                    System.out.println("out");
                    firstCondition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        }).start();
    }
}
複製程式碼

14,ReentrantLock(重入鎖)

公平鎖:公平鎖講究先來先到,執行緒在獲取鎖時,如果這個鎖的等待佇列中已經有執行緒在等待,那麼當前執行緒就會進入等待佇列中; 非公平鎖:不管是否有等待佇列,如果可以獲取鎖,則立刻佔有鎖物件。 1、ReentrantLock 擁有Synchronized相同的併發性和記憶體語義,此外還多了 鎖投票,定時鎖等候和中斷鎖等候

 執行緒A和B都要獲取物件O的鎖定,假設A獲取了物件O鎖,B將等待A釋放對O的鎖定,

 如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷

 如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以後,中斷等待,而幹別的事情
複製程式碼
  • ReentrantLock獲取鎖定與三種方式: a) lock(), 如果獲取了鎖立即返回,如果別的執行緒持有鎖,當前執行緒則一直處於休眠狀態,直到獲取鎖 b) tryLock(), 如果獲取了鎖立即返回true,如果別的執行緒正持有鎖,立即返回false; c)tryLock(long timeout,TimeUnit unit), 如果獲取了鎖定立即返回true,如果別的執行緒正持有鎖,會等待引數給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false; d) lockInterruptibly:如果獲取了鎖定立即返回,如果沒有獲取鎖定,當前執行緒處於休眠狀態,直到或者鎖定,或者當前執行緒被別的執行緒中斷 2、synchronized是在JVM層面上實現的,不但可以通過一些監控工具監控synchronized的鎖定,而且在程式碼執行時出現異常,JVM會自動釋放鎖定,但是使用Lock則不行,lock是通過程式碼實現的,要保證鎖定一定會被釋放,就必須將unLock()放到finally{}中 3、在資源競爭不是很激烈的情況下,Synchronized的效能要優於ReetrantLock,但是在資源競爭很激烈的情況下,Synchronized的效能會下降幾十倍,但是ReetrantLock的效能能維持常態; 目前來說,就是lock和unlock,其它的也不知道多少,等到再找時間再具體研究。

15 ReentrantReadWriteLock(讀寫鎖)

  • 讀寫鎖的機制: "讀-讀"不互斥 "讀-寫"互斥 "寫-寫"互斥
  • 即在任何時候必須保證: 只有一個執行緒在寫入; 執行緒正在讀取的時候,寫入操作等待; 執行緒正在寫入的時候,其他執行緒的寫入操作和讀取操作都要等待;
  • java.util.concurrent.locks包定義了兩個鎖類,ReentrantLock和ReentrantReadWriteLock類。 當有很多執行緒都從某個資料結構中讀取資料而很少有執行緒對其進行修改時,後者就很有用了。在這種情況下,允許讀取器執行緒共享訪問是合適的。當然,寫入器執行緒依然必須是互斥訪問的 下面是使用讀/寫鎖的必要步驟: (1) 建立一個ReentrantReadWriteLock物件 [java] view plain copy private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    (2)抽取讀鎖和寫鎖: [java] view plain copy private Lock readLock = rwl.readLock();//得到一個可被多個讀操作共用的讀鎖,但它會排斥所有寫操作
    private Lock writeLock = rwl.writeLock();//得到一個寫鎖,它會排斥所有其他的讀操作和寫操作
    (3) 對所有訪問者加讀鎖 [java] view plain copy public double getTotalBalance(){
    readLock.lock();
    try{...};
    finally{readLock.unlock();}
    } 對所有修改者加寫鎖 [java] view plain copy public void transfer(){
    writeLock.lock();
    try{...};
    finally{writeLock.unlock();}
    }

16, CountDownLatch、CyclicBarrier和Semaphore

其實,這三個都可以用於多執行緒中,很久以前就用過,只不過又忘記了。 其實就是,可以等待所有的執行緒都執行完,可以彙總一下。再此,再記錄一下。

17,Disruptor

暫時不研究,目前所有的專案都沒用過,用過的框架中不知道用沒有用,所以,僅此先記錄一下,如果以後工作中用到,再仔細研究。

18, 同步、非同步、阻塞、非阻塞

  • 1,同步和非同步是針對應用程式與核心的互動而言的。

  • 2,阻塞和非阻塞是針對於程式在訪問資料的時候,根據IO操作的就緒狀態來採取的不同方式,說白了是一種讀取或者寫入操作函式的實現方式,阻塞方式下讀取或者寫入函式將一直等待,而非阻塞方式下,讀取或者寫入函式會立即返回一個狀態值。

  • 由上描述基本可以總結一句簡短的話,同步和非同步是目的,阻塞和非阻塞是實現方式。


  • 1,同步: 指的是使用者程式觸發IO操作並等待或者輪詢的去檢視IO操作是否就緒。
  • 2,非同步: 非同步是指使用者程式觸發IO操作以後便開始做自己的事情,而當IO操作已經完成的時候會得到IO完成的通知(非同步的特點就是通知) 告訴朋友自己合適衣服的尺寸,大小,顏色,讓朋友委託去賣,然後自己可以去幹別的事。(使用非同步IO時,Java將IO讀寫委託給OS處理,需要將資料緩衝區地址和大小傳給OS)
  • 3,阻塞: 所謂阻塞方式的意思是指, 當試圖對該檔案描述符進行讀寫時, 如果當時沒有東西可讀,或者暫時不可寫, 程式就進入等待 狀態, 直到有東西可讀或者可寫為止 去公交站充值,發現這個時候,充值員不在(可能上廁所去了),然後我們就在這裡等待,一直等到充值員回來為止。(當然現實社會,可不是這樣,但是在計算機裡確實如此。)
  • 4,非阻塞: 非阻塞狀態下, 如果沒有東西可讀, 或者不可寫, 讀寫函式馬上返回,而不會等待, 銀行裡取款辦業務時,領取一張小票,領取完後我們自己可以玩玩手機,或者與別人聊聊天,當輪我們時,銀行的喇叭會通知,這時候我們就可以去了。

  • 同步阻塞IO(JAVA BIO): 同步並阻塞,伺服器實現模式為一個連線一個執行緒,即客戶端有連線請求時伺服器端就需要啟動一個執行緒進行處理,如果這個連線不做任何事情會造成不必要的執行緒開銷,當然可以通過執行緒池機制改善。

  • 同步非阻塞IO(Java NIO) : 同步非阻塞,伺服器實現模式為一個請求一個執行緒,即客戶端傳送的連線請求都會註冊到多路複用器上,多路複用器輪詢到連線有I/O請求時才啟動一個執行緒進行處理。使用者程式也需要時不時的詢問IO操作是否就緒,這就要求使用者程式不停的去詢問。

  • 非同步阻塞IO(Java NIO):
    此種方式下是指應用發起一個IO操作以後,不等待核心IO操作的完成,等核心完成IO操作以後會通知應用程式,這其實就是同步和非同步最關鍵的區別,同步必須等待或者主動的去詢問IO是否完成,那麼為什麼說是阻塞的呢?因為此時是通過select系統呼叫來完成的,而select函式本身的實現方式是阻塞的,而採用select函式有個好處就是它可以同時監聽多個檔案控制程式碼(如果從UNP的角度看,select屬於同步操作。因為select之後,程式還需要讀寫資料),從而提高系統的併發性!

  • (Java AIO(NIO.2))非同步非阻塞IO:
    在此種模式下,使用者程式只需要發起一個IO操作然後立即返回,等IO操作真正的完成以後,應用程式會得到IO操作完成的通知,此時使用者程式只需要對資料進行處理就好了,不需要進行實際的IO讀寫操作,因為真正的IO讀取或者寫入操作已經由核心完成了。

同步和非同步是相對於應用和核心的互動方式而言的,同步 需要主動去詢問,而非同步的時候核心在IO事件發生的時候通知應用程式,而阻塞和非阻塞僅僅是系統在呼叫系統呼叫的時候函式的實現方式而已。

  • For Example: 如果你想吃一份宮保雞丁蓋飯: 同步阻塞:你到飯館點餐,然後在那等著,還要一邊喊:好了沒啊! 同步非阻塞:在飯館點完餐,就去遛狗了。不過溜一會兒,就回飯館喊一聲:好了沒啊! 非同步阻塞:遛狗的時候,接到飯館電話,說飯做好了,讓您親自去拿。 非同步非阻塞:飯館打電話說,我們知道您的位置,一會給你送過來,安心遛狗就可以了。

19,AIO

目前理解起來有點沒有完全搞明白,等以後再搞一次。

20,&、|、^、<<、>>>

A = 0011 1100 B = 0000 1101

操作符 描述 例子
如果相對應位都是1,則結果為1,否則為0 (A&B),得到12,即0000 1100
| 如果相對應位都是0,則結果為0,否則為1 (A | B)得到61,即 0011 1101
^ 如果相對應位值相同,則結果為0,否則為1 (A ^ B)得到49,即 0011 0001
按位補運算子翻轉運算元的每一位,即0變成1,1變成0。 (〜A)得到-61,即1100 0011
<<  按位左移運算子。左運算元按位左移右運算元指定的位數。 A << 2得到240,即 1111 0000
>>  按位右移運算子。左運算元按位右移右運算元指定的位數。 A >> 2得到15即 1111
>>>  按位右移補零操作符。左運算元的值按右運算元指定的位數右移,移動得到的空位以零填充。 A>>>2得到15即0000 1111
> >* 負數的表示方法: 原碼:一個整數,按照絕對值大小轉換成的二進位制數,稱為原碼。 反碼:將二進位制數按位取反,所得的新二進位制數稱為原二進位制數的反碼。 補碼:反碼加1稱為補碼。 ![此處輸入圖片的描述][2]

21,java中byte轉換int時與0xff進行運算的原因

byte只有8位,如果byte中的數字為負數,那麼在預設轉化位int的時候會發生錯誤,0XFF預設是整形,運算後,就相當於顯式的轉換為int,不會出現二進位制上的錯誤。

⑴一個數為正,則它的原碼、反碼、補碼相同 ⑵一個數為負,剛符號位為1,其餘各位是對原碼取反,然後整個數加1

-1的原碼為 10000001 -1的反碼為 11111110

-1的補碼為 11111111

0的原碼為 00000000 0的反碼為 11111111(正零和負零的反碼相同)

0的補碼為 100000000(舍掉打頭的1,正零和負零的補碼相同)

Integer.toHexString的引數是int,如果不進行&0xff,那麼當一個byte會轉換成int時,由於int是32位,而byte只有8位這時會進行補位, 例如補碼11111111的十進位制數為-1轉換為int時變為11111111111111111111111111111111好多1啊,呵呵!即0xffffffff但是這個數是不對的,這種補位就會造成誤差。 和0xff相與後,高24位元就會被清0了,結果就對了。 Java中的一個byte,其範圍是-128~127的,而Integer.toHexString的引數本來是int,如果不進行&0xff,那麼當一個byte會轉換成int時,對於負數,會做位擴充套件,舉例來說,一個byte的-1(即0xff),會被轉換成int的-1(即0xffffffff),那麼轉化出的結果就不是我們想要的了。 而0xff預設是整形,所以,一個byte跟0xff相與會先將那個byte轉化成整形運算,這樣,結果中的高的24個位元就總會被清0,於是結果總是我們想要的

22,clone

  • shadow clone(淺克隆):通常只是對克隆的例項進行復制,但裡面的其他子物件,都是共用的。【簡單粗暴的理解:被克隆物件的基本資料型別屬性都拷貝了,但是被克隆物件的物件屬性,只是拷貝了個引用,該引用還是指向了堆中原來物件。一句話,如果克隆物件修改基本型別屬性,那麼被克隆物件不會被修改;如果克隆物件修改物件屬性,那麼被克隆物件就被修改了】
  • deep clone(深克隆): 克隆的時候會複製它的子物件的引用,裡面所有的變數和子物件都是又額外拷貝了一份。【簡單粗暴的理解:被克隆物件完全被複制了一份,克隆出來的物件無論修改什麼屬性,被克隆物件都不會變動】
  • 程式碼示例
package com.lvzhi.clone;

import java.io.Serializable;

/**
 * Created by lvzhi on 2018/1/14
 */
public class Wife implements Serializable {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

複製程式碼
package com.lvzhi.clone;

import java.io.*;

/**
 * Created by lvzhi on 2018/1/14
 */
public class Husband implements Cloneable, Serializable {
    private String name;
    private int age;
    private Wife wife;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Wife getWife() {
        return wife;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Husband husband = null;
        try{
            husband = (Husband)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }finally{
            return husband;
        }
    }

    /**
     * 利用序列化深克隆一個物件,把物件以及它的引用讀到流裡,在寫入其他的物件
     * 深克隆在clone方法中也可實現,只要是屬性是物件的,都要單獨呼叫clone方法,
     * 如果物件屬性很多,顯得很麻煩,所以一般都是用序列化方式來實現(不理解的
     * 可以單獨交流)
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Object deepClone() throws IOException,ClassNotFoundException {
        //將物件寫到流裡
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        //從流裡讀回來
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();
    }

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", wife=" + wife +
                '}';
    }
}

複製程式碼
package com.lvzhi.clone;

/**
 * Created by lvzhi on 2018/1/14
 */
public class Test {

    //淺克隆
    @org.junit.Test
    public void testShadowClone() throws Exception {
        Wife wife = new Wife();
        wife.setName("韓梅梅");
        wife.setAge(19);
        Husband husband = new Husband();
        husband.setName("李雷");
        husband.setAge(20);
        husband.setWife(wife);

        //克隆一個物件
        Husband husband1 = (Husband) husband.clone();

        System.out.println("husband,husband1是否為同一個物件:" + (husband == husband1));

        husband1.setAge(21);
        System.out.println(husband);
        System.out.println(husband1);
        System.out.println("以上結果看出,原物件的基本屬性值age,並沒有改變。");

        husband1.getWife().setAge(21);
        System.out.println(husband);
        System.out.println(husband1);
        System.out.println("以上結果可以看出,原物件和被克隆物件的物件屬性都改動了,wife的年齡都是21了。");
        System.out.println("不能做錯誤的測試,譬如:直接又new了一個wife物件,然後,husband1.setWife(newWife),這樣" +
                "husband中物件wife是不會改變的,想不明白的,再問我吧,呵呵");

    }

    //深克隆
    @org.junit.Test
    public void testDeepClone() throws Exception {
        Wife wife = new Wife();
        wife.setName("韓梅梅");
        wife.setAge(19);
        Husband husband = new Husband();
        husband.setName("李雷");
        husband.setAge(20);
        husband.setWife(wife);

        //克隆一個物件
        Husband husband1 = (Husband) husband.deepClone();

        System.out.println("husband,husband1是否為同一個物件:" + (husband == husband1));

        husband1.setAge(21);
        System.out.println(husband);
        System.out.println(husband1);
        System.out.println("以上結果看出,原物件的基本屬性值age,並沒有改變。");

        husband1.getWife().setAge(21);
        System.out.println(husband);
        System.out.println(husband1);
        System.out.println("以上結果可以看出,原物件什麼都沒變動");

    }

}

複製程式碼

23,jvm部分

parallel collector(throughput collector) 並行收集器:使用多執行緒的方式,利用多CUP來提高GC的效率,主要以到達一定的吞吐量為目標。 concurrent collector(concurrent low pause collector) 併發收集器:使用多執行緒的方式,利用多CUP來提高GC的效率,併發完成大部分工作,使得gc pause短。

以上兩者主要區別:throughput collector只在young area使用使用多執行緒,而concurrent low pause collector則在tenured generation也使用多執行緒

24,java的重寫(Override)與過載(Overload)

方法的重寫規則:

  • 引數列表必須完全與被重寫方法的相同;
  • 返回型別必須完全與被重寫方法的返回型別相同;
  • 訪問許可權不能比父類中被重寫的方法的訪問許可權更低。例如:如果父類的一個方法被宣告為public,那麼在子類中重寫該方法就不能宣告為protected。
  • 父類的成員方法只能被它的子類重寫。
  • 宣告為final的方法不能被重寫。
  • 宣告為static的方法不能被重寫,但是能夠被再次宣告。
  • 子類和父類在同一個包中,那麼子類可以重寫父類所有方法,除了宣告為private和final的方法。
  • 子類和父類不在同一個包中,那麼子類只能夠重寫父類的宣告為public和protected的非final方法。
  • 重寫的方法能夠丟擲任何非強制異常,無論被重寫的方法是否丟擲異常。但是,重寫的方法不能丟擲新的強制性異常,或者比被重寫方法宣告的更廣泛的強制性異常,反之則可以。
  • 構造方法不能被重寫。 如果不能繼承一個方法,則不能重寫這個方法。

方法過載規則:

  • 被過載的方法必須改變引數列表(引數個數或型別或順序不一樣)
  • 被過載的方法可以改變返回型別;
  • 被過載的方法可以改變訪問修飾符;
  • 被過載的方法可以宣告新的或更廣的檢查異常;
  • 方法能夠在同一個類中或者在一個子類中被過載。
  • 無法以返回值型別作為過載函式的區分標準。 如果不能繼承一個方法,則不能重寫這個方法。

相關文章