Java多線程高級特性(JDK8)

[TOC]

一笙僚、Java多線程

1.Java多線程基礎(chǔ)知識

Java 給多線程編程提供了內(nèi)置的支持。
一條線程指的是進(jìn)程中一個單一順序的控制流,一個進(jìn)程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)。

進(jìn)程:一個進(jìn)程包括由操作系統(tǒng)分配的內(nèi)存空間,包含一個或多個線程。一個線程不能獨(dú)立的存在,它必須是進(jìn)程的一部分禀倔。一個進(jìn)程一直運(yùn)行,直到所有的非守護(hù)線程都結(jié)束運(yùn)行后才能結(jié)束参淫。

2.Java線程的生命周期

  • 初始(NEW):新創(chuàng)建了一個線程對象救湖,但還沒有調(diào)用start()方法。
  • 運(yùn)行(RUNNABLE):Java線程中將就緒(ready)和運(yùn)行中(running)兩種狀態(tài)籠統(tǒng)的稱為“運(yùn)行”涎才。
    線程對象創(chuàng)建后鞋既,其他線程(比如main線程)調(diào)用了該對象的start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中,等待被線程調(diào)度選中涛救,獲取CPU的使用權(quán)畏邢,此時處于就緒狀態(tài)(ready)。就緒狀態(tài)的線程在獲得CPU時間片后變?yōu)檫\(yùn)行中狀態(tài)(running)检吆。
  • 阻塞(BLOCKED):表示線程阻塞于鎖舒萎。
    • 等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行 wait() 方法,使線程進(jìn)入到等待阻塞狀態(tài)
    • 同步阻塞:線程在獲取 synchronized 同步鎖失敗(因?yàn)橥芥i被其他線程占用)蹭沛。
    • 其他阻塞:通過調(diào)用線程的Sleep()或Join發(fā)出I/O請求臂寝,線程就會進(jìn)入到阻塞狀態(tài),當(dāng)Sleep()狀態(tài)超時摊灭,join()等待線程終止或超時咆贬,或者I/O處理完畢,線程就會進(jìn)入就緒狀態(tài)帚呼。
  • 等待(WAITING):進(jìn)入該狀態(tài)的線程需要等待其他線程做出一些特定動作(通知或中斷)掏缎。
  • 超時等待(TIMED_WAITING):該狀態(tài)不同于WAITING,它可以在指定的時間后自行返回煤杀。
  • 終止(TERMINATED):表示該線程已經(jīng)執(zhí)行完畢眷蜈。


    線程生命周期

3.線程的優(yōu)先級

每一個 Java 線程都有一個優(yōu)先級,這樣有助于操作系統(tǒng)確定線程的調(diào)度順序沈自。

Java 線程的優(yōu)先級是一個整數(shù)酌儒,其取值范圍是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。

默認(rèn)情況下枯途,每一個線程都會分配一個優(yōu)先級 NORM_PRIORITY(5)忌怎。

具有較高優(yōu)先級的線程對程序更重要,并且應(yīng)該在低優(yōu)先級的線程之前分配處理器資源酪夷。但是榴啸,線程優(yōu)先級不能保證線程執(zhí)行的順序,而且非常依賴于平臺捶索。

4.創(chuàng)建線程

Java創(chuàng)建線程提供了以下三種方法:

  • 通過實(shí)現(xiàn)Runnable接口
  • 通過繼承Thread類本身
  • 通過Callable和Future創(chuàng)建線程
4.1 通過實(shí)現(xiàn)Runnable接口來創(chuàng)建線程

通過Runnable接口需要重寫run() 方法

public void run()

在創(chuàng)建一個實(shí)現(xiàn) Runnable 接口的類之后插掂,你可以在類中實(shí)例化一個線程對象灰瞻。
Thread 定義了幾個構(gòu)造方法腥例,下面的這個是我們經(jīng)常使用的:

Thread(Runnable threadOb,String threadName);

這里,threadOb 是一個實(shí)現(xiàn) Runnable 接口的類的實(shí)例酝润,并且 threadName 指定新線程的名字燎竖。

新線程創(chuàng)建之后,你調(diào)用它的 start() 方法它才會運(yùn)行要销。

void start();
4.2 通過繼承Thread來創(chuàng)建線程

創(chuàng)建一個線程的第二種方法是創(chuàng)建一個新的類构回,該類繼承Thread類,然后創(chuàng)建一個該類的實(shí)例。
繼承類必須重寫 run() 方法纤掸,該方法是新線程的入口點(diǎn)脐供。它也必須調(diào)用 start() 方法才能執(zhí)行。

Thread方法:

序號 方法描述
1 public void start() 使該線程開始執(zhí)行借跪;Java 虛擬機(jī)調(diào)用該線程的 run 方法政己。
2 public void run() 如果該線程是使用獨(dú)立的 Runnable 運(yùn)行對象構(gòu)造的,則調(diào)用該 Runnable 對象的 run 方法掏愁;否則歇由,該方法不執(zhí)行任何操作并返回。
3 public final void setName(String name) 改變線程名稱果港,使之與參數(shù) name 相同沦泌。
4 public final void setPriority(int priority) 更改線程的優(yōu)先級。
5 public final void setDaemon(boolean on) 將該線程標(biāo)記為守護(hù)線程或用戶線程辛掠。
6 public final void join(long millisec) 等待該線程終止的時間最長為 millis 毫秒谢谦。
7 public void interrupt() 中斷線程。
8 public final boolean isAlive()

Thread靜態(tài)類方法

序號 方法描述
1 public static void yield() 暫停當(dāng)前正在執(zhí)行的線程對象萝衩,并執(zhí)行其他線程他宛。
2 public static void sleep(long millisec) 在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),此操作受到系統(tǒng)計(jì)時器和調(diào)度程序精度和準(zhǔn)確性的影響欠气。
3 public static boolean holdsLock(Object x) 當(dāng)且僅當(dāng)當(dāng)前線程在指定的對象上保持監(jiān)視器鎖時厅各,才返回 true。
4 public static Thread currentThread() 返回對當(dāng)前正在執(zhí)行的線程對象的引用预柒。
5 public static void dumpStack() 將當(dāng)前線程的堆棧跟蹤打印至標(biāo)準(zhǔn)錯誤流队塘。
4.3 通過Callable和Future創(chuàng)建線程
  • 創(chuàng)建 Callable 接口的實(shí)現(xiàn)類,并實(shí)現(xiàn)call()方法宜鸯,該call()方法將作為線程執(zhí)行體憔古,并且有返回值。
  • 創(chuàng)建 Callable 實(shí)現(xiàn)類的實(shí)例淋袖,使用FutureTask類來包裝Callable對象鸿市,該FutureTask對象封裝了該 Callable 對象的 call() 方法的返回值。
  • 使用 FutureTask 對象作為 Thread 對象的 target 創(chuàng)建并啟動新線程即碗。
  • 調(diào)用 FutureTask 對象的 get() 方法來獲得子線程執(zhí)行結(jié)束后的返回值焰情。
    For Example:
import java.util.concurrent.*;
public class Test {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        executorService.submit(futureTask);
        executorService.shutdown();
        
        System.out.println("主線程在執(zhí)行任務(wù)...");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
         
        try {
            System.out.println("task運(yùn)行結(jié)果:"+futureTask.get());
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        } catch (ExecutionException ex) {
            ex.printStackTrace();
        }
         
        System.out.println("所有任務(wù)執(zhí)行完畢");
    }
}
class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println("子線程在執(zhí)行任務(wù)...");
        //模擬任務(wù)耗時
        Thread.sleep(5000);
        return 1000;
    }
}

5.線程的取消和中斷

  • 不安全的取消
    Stop()/suspend()/resume()是過期的API,容易導(dǎo)致死鎖或者數(shù)據(jù)不一致
  • 安全的終止線程
    • interrupt() 中斷線程剥懒,本質(zhì)是將線程的中斷標(biāo)志位設(shè)為true内舟,其他線程向需要中斷的線程打個招呼。是否真正進(jìn)行中斷由線程自己決定
    • isInterrupted() 線程檢查自己的中斷標(biāo)志位
    • 靜態(tài)方法Thread.interrupted() 將中斷標(biāo)志位復(fù)位為false

6.常用的方法理解

  • run()和start()

    run就是一個普通的方法初橘,跟其他類的實(shí)例方法沒有任何區(qū)別验游。
  • Sleep <Br>
    不會釋放鎖充岛,所以我們在用sleep時,要把sleep放在同步代碼塊的外面耕蝉。
  • yield()<Br>
    當(dāng)前線程出讓cpu占有權(quán)崔梗,當(dāng)前線程變成了可運(yùn)行狀態(tài),下一時刻仍然可能被cpu選中垒在,不會釋放鎖炒俱。
  • wait()和 notify()/notiyfAll()

    調(diào)用以前,當(dāng)前線程必須要持有鎖爪膊,調(diào)用了wait() notify()/notiyfAll()會釋放鎖权悟。

通知機(jī)制:<Br>
線程 A調(diào)用了對象O的wait方法進(jìn)入等待狀態(tài),線程 B調(diào)用了對象O的notify方法進(jìn)行喚醒推盛,喚醒的是在對象O上wait的線程(比如線程A)
notify() 喚醒一個線程峦阁,喚醒哪一個完全看cpu的心情(謹(jǐn)慎使用)

notiyfAll() 所有在對象O上wait的線程全部喚醒(應(yīng)該用notiyfAll())

6.線程關(guān)鍵字

6.1 volatile

多個線程同時訪問一個共享的變量的時候,每個線程的工作內(nèi)存有這個變量的一個拷貝耘成,變量本身還是保存在共享內(nèi)存中榔昔。
Violate修飾字段,對這個變量的訪問必須要從共享內(nèi)存刷新一次瘪菌。最新的修改寫回共享內(nèi)存撒会。可以保證字段的可見性师妙。絕對不是線程安全的诵肛,沒有操作的原子性。
適用場景:

1默穴、一個線程寫怔檩,多個線程讀;

2蓄诽、volatile變量的變化很固定

public class VolatileThread implements Runnable {

    private volatile  int a= 0;

    @Override
    public void run() {
//        synchronized (this){
            a=a+1;
            System.out.println(Thread.currentThread().getName()+"----"+a);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            a=a+1;
            System.out.println(Thread.currentThread().getName()+"----"+a);

//        }
    }
}
public class VolatileTest {
    public static void main(String[] args) {
        VolatileThread volatileThread = new VolatileThread();

        Thread t1 = new Thread(volatileThread);
        Thread t2 = new Thread(volatileThread);
        Thread t3 = new Thread(volatileThread);
        Thread t4 = new Thread(volatileThread);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
6.2 synchronized

關(guān)鍵字synchronized可以修飾方法或者以同步塊的形式來進(jìn)行使用薛训,它主要確保多個線程在同一個時刻,只能有一個線程處于方法或者同步塊中仑氛,它保證了線程對變量訪問的可見性和排他性乙埃,又稱為內(nèi)置鎖機(jī)制。
Synchronized的類鎖和對象鎖锯岖,本質(zhì)上是兩把鎖介袜,類鎖實(shí)際鎖的是每一個類的class對象。對象鎖鎖的是當(dāng)前對象實(shí)例嚎莉。

public class InstanceAndClass {

    //測試類鎖
    private static class TestClassSyn extends Thread{
        @Override
        public void run() {
            System.out.println("TestClass is going...");
            synClass();
        }
    }

    //測試對象鎖
    private static class TestInstanceSyn extends Thread{
        private InstanceAndClass instanceAndClass;

        public TestInstanceSyn(InstanceAndClass instanceAndClass) {
            this.instanceAndClass = instanceAndClass;
        }

        @Override
        public void run() {
            System.out.println("TestInstance is going..."+instanceAndClass);
            instanceAndClass.synInstance();
        }

    }

    //測試對象鎖
    private static class TestInstance2Syn implements Runnable{
        private InstanceAndClass instanceAndClass;

        public TestInstance2Syn(InstanceAndClass instanceAndClass) {
            this.instanceAndClass = instanceAndClass;
        }
        @Override
        public void run() {
            System.out.println("TestInstance2 is going..."+instanceAndClass);
            instanceAndClass.synInstance2();
        }
    }

    //鎖對象的方法
    private synchronized void synInstance(){
        SleepUtils.second(3);
        System.out.println("synInstance is going...");
        SleepUtils.second(3);
        System.out.println("synInstance ended");
    }

    //鎖對象的方法
    private synchronized void synInstance2(){
        SleepUtils.second(3);
        System.out.println("synInstance2 going...");
        SleepUtils.second(3);
        System.out.println("synInstance2 ended");
    }

    //鎖類的方法
    private static synchronized void synClass(){
        SleepUtils.second(1);
        System.out.println("synClass going...");
        SleepUtils.second(1);
    }

    public static void main(String[] args) {
        InstanceAndClass instanceAndClass = new InstanceAndClass();
        Thread t1 = new TestClassSyn();
        Thread t2 = new Thread(new TestInstanceSyn(instanceAndClass));
        Thread t3 = new Thread(new TestInstance2Syn(instanceAndClass));
        t2.start();
        t3.start();
        SleepUtils.second(1);
        t1.start();
    }

}
6.3等待和通知機(jī)制
  • 等待方原則:

1米酬、獲取對象鎖

2、如果條件不滿足趋箩,調(diào)用對象的wait方法赃额,被通知后依然要檢查條件是否滿足

3、條件滿足以后叫确,才能執(zhí)行相關(guān)的業(yè)務(wù)邏輯

Synchronized(對象){
    While(條件不滿足){
    對象.wait()
}
業(yè)務(wù)邏輯處理
}

  • 通知方原則

1、獲得對象的鎖竹勉;<Br>
2飞盆、 改變條件;<Br>
3次乓、 通知所有等待在對象的線程

Synchronized(對象){
    業(yè)務(wù)邏輯處理吓歇,改變條件
    對象.notify/notifyAll
}

6.4管道輸入輸出流

文件輸入輸出,網(wǎng)絡(luò)輸入輸出票腰,管道輸入輸出流用于線程中間的數(shù)據(jù)傳遞城看,傳輸媒介的內(nèi)存
pipedOutputStream/input 面向的字節(jié)

pipedReader/Writer 面向的是字符

只適合線程間一對一的通信,適用范圍較狹窄杏慰。

public class PipeTransfer {

    private static class Print implements Runnable{
        private PipedReader in;

        public Print(PipedReader in) {
            this.in = in;
        }

        @Override
        public void run() {
           int receive =0;
            try {
                while((receive=in.read())!=-1){
                    System.out.println((char) receive);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        PipedWriter out = new PipedWriter();
        PipedReader in = new PipedReader();
        //必須進(jìn)行連接
        out.connect(in);

        Thread t1 = new Thread(new Print(in),"PrintThread");
        t1.start();
        int receive =0;
        try {
            while((receive=System.in.read())!=-1){
                out.write(receive);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            out.close();
        }
    }

}

6.5 join方法

線程A测柠,執(zhí)行了thread.join(),線程A等待thread線程終止了以后,A在join后面的語句才會繼續(xù)執(zhí)行

6.6 一些有用的方法

很多時候我們希望在元素不存在時插入元素缘滥,我們一般會像下面那樣寫代碼

synchronized(map){
  if (map.get(key) == null){
      return map.put(key, value);
  } else{
      return map.get(key);
  }
}

putIfAbsent(key,value)方法原子性的實(shí)現(xiàn)了同樣的功能

V putIfAbsent(K key, V value)

如果key對應(yīng)的value不存在轰胁,則put進(jìn)去,返回null朝扼。否則不put赃阀,返回已存在的value。
boolean remove(Object key, Object value)
如果key對應(yīng)的值是value擎颖,則移除K-V凹耙,返回true。否則不移除肠仪,返回false肖抱。
boolean replace(K key, V oldValue, V newValue)
如果key對應(yīng)的當(dāng)前值是oldValue,則替換為newValue异旧,返回true意述。否則不替換,返回false吮蛹。

二荤崇、并發(fā)工具和并發(fā)容器

1.Hash

散列,任意長度的輸入潮针,通過一種算法术荤,變換成固定長度的輸出。屬于壓縮的映射每篷。Md5瓣戚,Sha端圈,取余都是散列算法,ConcurrentHashMap中是wang/jenkins算法

2.ConcurrentHashMap

在多線程環(huán)境下子库,使用HashMap進(jìn)行put操作會引起死循環(huán)舱权,導(dǎo)致CPU利用率接近100%,HashMap在并發(fā)執(zhí)行put操作時會引起死循環(huán)仑嗅,是因?yàn)槎嗑€程會導(dǎo)致HashMap的Entry鏈表形成環(huán)形數(shù)據(jù)結(jié)構(gòu)宴倍,一旦形成環(huán)形數(shù)據(jù)結(jié)構(gòu),Entry的next節(jié)點(diǎn)永遠(yuǎn)不為空仓技,就會產(chǎn)生死循環(huán)獲取Entry鸵贬。

HashTable容器使用synchronized來保證線程安全,但在線程競爭激烈的情況下HashTable的效率非常低下脖捻。因?yàn)楫?dāng)一個線程訪問HashTable的同步方法阔逼,其他線程也訪問HashTable的同步方法時,會進(jìn)入阻塞或輪詢狀態(tài)郭变。如線程1使用put進(jìn)行元素添加颜价,線程2不但不能使用put方法添加元素,也不能使用get方法來獲取元素诉濒,所以競爭越激烈效率越低周伦。

3.ConcurrentHashMap在JDK1.7中的實(shí)現(xiàn)

分段鎖的設(shè)計(jì)思路

ConcurrentHashMap是由Segment數(shù)組結(jié)構(gòu)和HashEntry數(shù)組結(jié)構(gòu)組成。Segment實(shí)際是一種可重入鎖(ReentrantLock)未荒,HashEntry則用于存儲鍵值對數(shù)據(jù)专挪。一個ConcurrentHashMap里包含一個Segment數(shù)組。Segment的結(jié)構(gòu)和HashMap類似片排,是一種數(shù)組和鏈表結(jié)構(gòu)寨腔。一個Segment里包含一個HashEntry數(shù)組,每個HashEntry是一個鏈表結(jié)構(gòu)的元素率寡,每個Segment守護(hù)著一個HashEntry數(shù)組里的元素迫卢,當(dāng)對HashEntry數(shù)組的數(shù)據(jù)進(jìn)行修改時,必須首先獲得與它對應(yīng)的Segment鎖冶共。

ConcurrentHashMap

ConcurrentHashMap初始化方法是通過initialCapacity乾蛤、loadFactor和concurrencyLevel(參數(shù)concurrencyLevel是用戶估計(jì)的并發(fā)級別,就是說你覺得最多有多少線程共同修改這個map捅僵,根據(jù)這個來確定Segment數(shù)組的大小concurrencyLevel默認(rèn)是DEFAULT_CONCURRENCY_LEVEL = 16;)家卖。

ConcurrentHashMap完全允許多個讀操作并發(fā)進(jìn)行,讀操作并不需要加鎖庙楚。ConcurrentHashMap實(shí)現(xiàn)技術(shù)是保證HashEntry幾乎是不可變的上荡。HashEntry代表每個hash鏈中的一個節(jié)點(diǎn),可以看到其中的對象屬性要么是final的馒闷,要么是volatile的酪捡。

4.ConcurrentHashMap在1.8下的實(shí)現(xiàn)

改進(jìn)一:取消segments字段,直接采用transient volatile HashEntry<K,V>[]<Br> table保存數(shù)據(jù)叁征,采用table數(shù)組元素作為鎖,從而實(shí)現(xiàn)了對每一行數(shù)據(jù)進(jìn)行加鎖沛善,進(jìn)一步減少并發(fā)沖突的概率航揉。<Br>
改進(jìn)二:將原先table數(shù)組+單向鏈表的數(shù)據(jù)結(jié)構(gòu)塞祈,變更為table數(shù)組+單向鏈表+紅黑樹的結(jié)構(gòu)金刁。對于個數(shù)超過8(默認(rèn)值)的列表,jdk1.8中采用了紅黑樹的結(jié)構(gòu)议薪,那么查詢的時間復(fù)雜度可以降低到O(logN)尤蛮,可以改進(jìn)性能。


Table數(shù)組+單向鏈表+紅黑樹

5.ConcurrentSkipListMap和ConcurrentSkipListSet

ConcurrentSkipListMap TreeMap的并發(fā)實(shí)現(xiàn)
有序Map
ConcurrentSkipListSet TreeSet的并發(fā)實(shí)現(xiàn)
有序Set

6.SkipList

二分查找和AVL樹查找
二分查找要求元素可以隨機(jī)訪問斯议,所以決定了需要把元素存儲在連續(xù)內(nèi)存产捞。這樣查找確實(shí)很快,但是插入和刪除元素的時候哼御,為了保證元素的有序性坯临,就需要大量的移動元素了。

如果需要的是一個能夠進(jìn)行二分查找恋昼,又能快速添加和刪除元素的數(shù)據(jù)結(jié)構(gòu)看靠,首先就是二叉查找樹,二叉查找樹在最壞情況下可能變成一個鏈表液肌。

于是挟炬,就出現(xiàn)了平衡二叉樹,根據(jù)平衡算法的不同有AVL樹嗦哆,B-Tree谤祖,B+Tree,紅黑樹等老速,但是AVL樹實(shí)現(xiàn)起來比較復(fù)雜粥喜,平衡操作較難理解,這時候就可以用SkipList跳躍表結(jié)構(gòu)橘券。

傳統(tǒng)意義的單鏈表是一個線性結(jié)構(gòu)额湘,向有序的鏈表中插入一個節(jié)點(diǎn)需要O(n)的時間,查找操作需要O(n)的時間约郁。

跳表

如果我們使用上圖所示的跳躍表缩挑,就可以減少查找所需時間為O(n/2),因?yàn)槲覀兛梢韵韧ㄟ^每個節(jié)點(diǎn)的最上面的指針先進(jìn)行查找鬓梅,這樣子就能跳過一半的節(jié)點(diǎn)供置。

比如我們想查找19,首先和6比較绽快,大于6之后芥丧,在和9進(jìn)行比較紧阔,然后在和12進(jìn)行比較......最后比較到21的時候,發(fā)現(xiàn)21大于19续担,說明查找的點(diǎn)在17和21之間擅耽,從這個過程中,我們可以看出物遇,查找的時候跳過了3乖仇、7、12等點(diǎn)询兴,因此查找的復(fù)雜度為O(n/2)乃沙。

跳躍表其實(shí)也是一種通過“空間來換取時間”的一個算法,通過在每個節(jié)點(diǎn)中增加了向前的指針诗舰,從而提升查找的效率警儒。
跳躍表又被稱為概率,或者說是隨機(jī)化的數(shù)據(jù)結(jié)構(gòu)眶根,目前開源軟件 Redis 和 lucence都有用到它蜀铲。

7.ConcurrentLinkedQueue 無界非阻塞隊(duì)列

常用方法:

Add,offer:添加元素

Peek:get頭元素并不把元素拿走

poll():get頭元素把元素拿走

8.CopyOnWriteArrayList和CopyOnWriteArraySet

寫的時候進(jìn)行復(fù)制,可以進(jìn)行并發(fā)的讀属百。

適用讀多寫少的場景:比如白名單记劝,黑名單,商品類目的訪問和更新場景诸老,假如我們有一個搜索網(wǎng)站隆夯,用戶在這個網(wǎng)站的搜索框中,輸入關(guān)鍵字搜索內(nèi)容别伏,但是某些關(guān)鍵字不允許被搜索蹄衷。這些不能被搜索的關(guān)鍵字會被放在一個黑名單當(dāng)中,黑名單每天晚上更新一次厘肮。當(dāng)用戶搜索時愧口,會檢查當(dāng)前關(guān)鍵字在不在黑名單當(dāng)中,如果在类茂,則提示不能搜索耍属。

弱點(diǎn):內(nèi)存占用高,數(shù)據(jù)一致性弱

Tips 多用isEmpty()少用 Size()

9.Lock

Lock不是Java語言內(nèi)置的巩检,synchronized是Java語言的關(guān)鍵字厚骗,因此是內(nèi)置特性。Lock是一個類兢哭,通過這個類可以實(shí)現(xiàn)同步訪問领舰;

Lock和synchronized有一點(diǎn)非常大的不同,采用synchronized不需要用戶去手動釋放鎖,當(dāng)synchronized方法或者synchronized代碼塊執(zhí)行完之后冲秽,系統(tǒng)會自動讓線程釋放對鎖的占用舍咖;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖锉桑,就有可能導(dǎo)致出現(xiàn)死鎖現(xiàn)象排霉。

1.接口類

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
  • lock 獲取鎖
Lock lock =new ReentrantLock();  # ReetrantLock 可重入鎖
lock.lock();
try{
    //處理任務(wù)
}catch(Exception ex){
     
}finally{
    lock.unlock();   //釋放鎖
}
  • tryLock() 嘗試獲取鎖 返回類型: boolean
Lock lock = ...;
if(lock.tryLock()) {
     try{
         //處理任務(wù)
     }catch(Exception ex){
         
     }finally{
         lock.unlock();   //釋放鎖
     } 
}else {
    //如果不能獲取鎖,則直接做其他事情
}
  • lockInterruptibly() 線程中斷

三民轴、阻塞對列

隊(duì)列滿額攻柠,線程數(shù)據(jù)無法插入,就會被阻塞等待數(shù)據(jù)插入杉武,直到隊(duì)列可以插入為止辙诞,當(dāng)隊(duì)列為空時辙售,會被阻塞轻抱,直到數(shù)據(jù)插入后才可以喚醒線程移除。

1.常用的方法

方法 拋出異常 返回值 一直阻塞 超時退出
插入 Add offer put offer
移除 remove poll take poll
檢查 element peek 沒有 沒有

2.常用阻塞隊(duì)列

  • ArrayBlockingQueue

數(shù)組結(jié)構(gòu)組成有界阻塞隊(duì)列旦部,先進(jìn)先出原則祈搜,初始化必須傳大小,take和put時候用的同一把鎖士八。

  • LinkedBlockingQueue

鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列,先進(jìn)先出原則容燕,初始化可以不傳大小,put婚度,take鎖分離

  • PriorityBlockingQueue

支持優(yōu)先級排序的無界阻塞隊(duì)列,自然順序升序排列蘸秘,更改順序:類自己實(shí)現(xiàn)compareTo()方法,初始化PriorityBlockingQueue指定一個比較器Comparator

  • DelayQueue

使用了優(yōu)先級隊(duì)列的無界阻塞隊(duì)列,支持延時獲取蝗茁,隊(duì)列里的元素要實(shí)現(xiàn)Delay接口醋虏。DelayQueue非常有用,可以將DelayQueue運(yùn)用在以下應(yīng)用場景:

緩存系統(tǒng)的設(shè)計(jì):可以用DelayQueue保存緩存元素的有效期哮翘,使用一個線程循環(huán)查詢DelayQueue颈嚼,一旦能從DelayQueue中獲取元素時,表示緩存有效期到了饭寺。還有訂單到期阻课,限時支付等.

  • SynchronousQueue

不存儲元素的阻塞隊(duì)列,每個put操作必須要等take操作

  • LinkedTransferQueue

鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列,Transfer,tryTransfer,生產(chǎn)者put時艰匙,當(dāng)前有消費(fèi)者take限煞,生產(chǎn)者直接把元素傳給消費(fèi)者

  • LinkedBlockingDeque

鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列,可以在隊(duì)列的兩端插入和移除,xxxFirst頭部操作,xxxLast尾部操作员凝。工作竊取模式署驻。

3.阻塞隊(duì)的實(shí)現(xiàn)原理

Java 阻塞隊(duì)列使用 Condititon、Lock,在元素進(jìn)入使用等待wait()硕舆,出隊(duì)使用喚醒秽荞。

4.For/Join框架

kyijQs.png

把大任務(wù)拆分成很多的小任務(wù),匯總每個小任務(wù)的結(jié)果得到大任務(wù)的結(jié)果抚官。
Fork/Join使用兩個類來完成以上兩件事情扬跋。

①ForkJoinTask:我們要使用ForkJoin框架,必須首先創(chuàng)建一個ForkJoin任務(wù)凌节。它提供在任務(wù)
中執(zhí)行fork()和join()操作的機(jī)制钦听。通常情況下,我們不需要直接繼承ForkJoinTask類倍奢,只需要繼承它的子類朴上,F(xiàn)ork/Join框架提供了以下兩個子類。
·RecursiveAction:用于沒有返回結(jié)果的任務(wù)卒煞。
·RecursiveTask:用于有返回結(jié)果的任務(wù)痪宰。

②ForkJoinPool:ForkJoinTask需要通過ForkJoinPool來執(zhí)行。
Fork/Join有同步和異步兩種方式畔裕。

5.CountDownLatch

允許一個或多個線程等待其他線程完成操作衣撬。CountDownLatch的構(gòu)造函數(shù)接收一個int類型的參數(shù)作為計(jì)數(shù)器,如果你想等待N個點(diǎn)完成扮饶,這里就傳入N具练。當(dāng)我們調(diào)用CountDownLatch的countDown方法時,N就會減1甜无,CountDownLatch的await方法會阻塞當(dāng)前線程扛点,直到N變成零。由于countDown方法可以用在任何地方岂丘,所以這里說的N個點(diǎn)陵究,可以是N個線程,也可以是1個線程里的N個執(zhí)行步驟元潘。用在多個線程時畔乙,只需要把這個CountDownLatch的引用傳遞到線程里即可。

6.CyclicBarrier

CyclicBarrier的字面意思是可循環(huán)使用(Cyclic)的屏障(Barrier)翩概。它要做的事情是牲距,讓一組線程到達(dá)一個屏障(也可以叫同步點(diǎn))時被阻塞,直到最后一個線程到達(dá)屏障時钥庇,屏障才會開門牍鞠,所有被屏障攔截的線程才會繼續(xù)運(yùn)行。CyclicBarrier默認(rèn)的構(gòu)造方法是CyclicBarrier(int parties)评姨,其參數(shù)表示屏障攔截的線程數(shù)量难述,每個線程調(diào)用await方法告訴CyclicBarrier我已經(jīng)到達(dá)了屏障萤晴,然后當(dāng)前線程被阻塞。

CyclicBarrier還提供一個更高級的構(gòu)造函數(shù)CyclicBarrier(int parties胁后,Runnable barrierAction)店读,用于在線程到達(dá)屏障時,優(yōu)先執(zhí)行barrierAction攀芯,方便處理更復(fù)雜的業(yè)務(wù)場景屯断。
CyclicBarrier可以用于多線程計(jì)算數(shù)據(jù),最后合并計(jì)算結(jié)果的場景侣诺。

CyclicBarrier和CountDownLatch的區(qū)別

CountDownLatch的計(jì)數(shù)器只能使用一次殖演,而CyclicBarrier的計(jì)數(shù)器可以使用reset()方法重
置,CountDownLatch.await一般阻塞主線程年鸳,所有的工作線程執(zhí)行countDown趴久,而CyclicBarrierton通過工作線程調(diào)用await從而阻塞工作線程,直到所有工作線程達(dá)到屏障搔确。

四 彼棍、Java 8高級特性

1、Lambda 表達(dá)式

Lambda 是一個匿名函數(shù)妥箕,我們可以把 Lambda 表達(dá)式理解為是一段可以傳遞的代碼(將代碼像數(shù)據(jù)一樣進(jìn)行傳遞)滥酥。可以寫出更簡潔畦幢、更靈活的代碼。作為一種更緊湊的代碼風(fēng)格缆蝉,使Java的語言表達(dá)能力得到了提升宇葱。

  • Style 1 無參,無返回值刊头,Lambda只需要一條語句
Runnable runnable = () -> System.out.println("hello Lambda");
  • Style 2 一個參數(shù)
Consumer<String> con = (t) -> System.out.println(t);
  • Style 3 參數(shù)省略括號
Consumer<String> con = t -> System.out.println(t);
  • Style 4 2個參數(shù)黍瞧,具有返回值
Comparator<Integer> comparator = (x,y) -> {
        System.out.println("相加結(jié)果是:"+(x+y));
        return Integer.compare(x,y);
    };
  • Style 5 省略return和大括號
Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);
  • Style 6 類型推斷寫法
Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);

2.Stream API

Stream 是 Java8 中處理集合的關(guān)鍵抽象概念,它可以指定你希望對集合進(jìn)行的操作原杂,可以執(zhí)行非常復(fù)雜的查找印颤、過濾和映射數(shù)據(jù)等操作。使用Stream API 對集合數(shù)據(jù)進(jìn)行操作穿肄,就類似于使用 SQL 執(zhí)行的數(shù)據(jù)庫查詢年局。也可以使用 Stream API 來并行執(zhí)行操作。簡而言之咸产,Stream API 提供了一種高效且易于使用的處理數(shù)據(jù)的方式矢否。

  • 創(chuàng)建Stream

Java8 中的 Collection 接口被擴(kuò)展,提供了兩個獲取流的方法:

 default Stream stream() :   #返回一個順序流 
 default Stream parallelStream() : # 返回一個并行流

3.Stream 中間操作

原始集合:

List<Apple> appleList = new ArrayList<>();//存放apple對象集合

Apple apple1 =  new Apple(1,"蘋果1",new BigDecimal("3.25"),10);
Apple apple12 = new Apple(1,"蘋果2",new BigDecimal("1.35"),20);
Apple apple2 =  new Apple(2,"香蕉",new BigDecimal("2.89"),30);
Apple apple3 =  new Apple(3,"荔枝",new BigDecimal("9.99"),40);

appleList.add(apple1);
appleList.add(apple12);
appleList.add(apple2);
appleList.add(apple3);
  • List轉(zhuǎn)Map
/**
 * List -> Map
 * 需要注意的是:
 * toMap 如果集合對象有重復(fù)的key脑溢,會報錯Duplicate key ....
 *  apple1,apple12的id都為1僵朗。
 *  可以用 (k1,k2)->k1 來設(shè)置,如果有重復(fù)的key,則保留key1,舍棄key2
 */
Map<Integer, Apple> appleMap = appleList.stream().collect(Collectors.toMap(Apple::getId, a -> a,(k1,k2)->k1));

{1=Apple{id=1, name='蘋果1', money=3.25, num=10}, 2=Apple{id=2, name='香蕉', money=2.89, num=30}, 3=Apple{id=3, name='荔枝', money=9.99, num=40}}
  • 分組
//List 以ID分組 Map<Integer,List<Apple>>
Map<Integer, List<Apple>> groupBy = appleList.stream().collect(Collectors.groupingBy(Apple::getId));

System.err.println("groupBy:"+groupBy);

{1=[Apple{id=1, name='蘋果1', money=3.25, num=10}, Apple{id=1, name='蘋果2', money=1.35, num=20}], 2=[Apple{id=2, name='香蕉', money=2.89, num=30}], 3=[Apple{id=3, name='荔枝', money=9.99, num=40}]}
  • 過濾 filter
//過濾出符合條件的數(shù)據(jù)
List<Apple> filterList = appleList.stream().filter(a -> a.getName().equals("香蕉")).collect(Collectors.toList());

System.err.println("filterList:"+filterList);

[Apple{id=2, name='香蕉', money=2.89, num=30}]
  • 求和
# BigDecimal:
BigDecimal totalMoney = appleList.stream().map(Apple::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
System.err.println("totalMoney:"+totalMoney);  //totalMoney:17.48

# Integer
int sum = appleList.stream().mapToInt(Apple::getNum).sum();
System.err.println("sum:"+sum);  //sum:100
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市验庙,隨后出現(xiàn)的幾起案子顶吮,更是在濱河造成了極大的恐慌,老刑警劉巖粪薛,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件云矫,死亡現(xiàn)場離奇詭異,居然都是意外死亡汗菜,警方通過查閱死者的電腦和手機(jī)让禀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陨界,“玉大人巡揍,你說我怎么就攤上這事【瘢” “怎么了腮敌?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長俏扩。 經(jīng)常有香客問我糜工,道長,這世上最難降的妖魔是什么录淡? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任捌木,我火速辦了婚禮,結(jié)果婚禮上嫉戚,老公的妹妹穿的比我還像新娘刨裆。我一直安慰自己,他們只是感情好彬檀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布帆啃。 她就那樣靜靜地躺著,像睡著了一般窍帝。 火紅的嫁衣襯著肌膚如雪努潘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天坤学,我揣著相機(jī)與錄音疯坤,去河邊找鬼。 笑死拥峦,一個胖子當(dāng)著我的面吹牛贴膘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播略号,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼刑峡,長吁一口氣:“原來是場噩夢啊……” “哼洋闽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起突梦,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诫舅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宫患,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刊懈,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年娃闲,在試婚紗的時候發(fā)現(xiàn)自己被綠了虚汛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡皇帮,死狀恐怖卷哩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情属拾,我是刑警寧澤将谊,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站渐白,受9級特大地震影響尊浓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纯衍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一栋齿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧托酸,春花似錦褒颈、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽堡掏。三九已至应结,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間泉唁,已是汗流浹背鹅龄。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亭畜,地道東北人扮休。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像拴鸵,于是被迫代替她去往敵國和親玷坠。 傳聞我的和親對象是個殘疾皇子蜗搔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Java-Review-Note——4.多線程 標(biāo)簽: JavaStudy PS:本來是分開三篇的,后來想想還是整...
    coder_pig閱讀 1,653評論 2 17
  • layout: posttitle: 《Java并發(fā)編程的藝術(shù)》筆記categories: Javaexcerpt...
    xiaogmail閱讀 5,824評論 1 19
  • Java SE 基礎(chǔ): 封裝八堡、繼承樟凄、多態(tài) 封裝: 概念:就是把對象的屬性和操作(或服務(wù))結(jié)合為一個獨(dú)立的整體,并盡...
    Jayden_Cao閱讀 2,110評論 0 8
  • PS 扁平化設(shè)計(jì) 首選項(xiàng):Common + K兄渺; 顯示/隱藏標(biāo)尺:Common + R缝龄; 修改標(biāo)尺單位:鼠標(biāo)移至標(biāo)...
    萌萌_1014閱讀 396評論 0 0
  • 這幾日,滬上降溫挂谍。秋風(fēng)蕭瑟叔壤,吹落了滿枝濃密的桂花,金黃滿地口叙,落葉紛紛炼绘,簌簌而下。一絲肅殺在心中升騰庐扫,秋意濃了饭望。 秋...
    鐵嫵閱讀 1,349評論 77 63