1. 程式人生 > >Java多執行緒之執行緒安全與非同步執行

Java多執行緒之執行緒安全與非同步執行

多執行緒併發修改一個數據結構,很容易破壞這個資料結構,如散列表。鎖能夠保護共享資料結構,但選擇執行緒安全的實現更好更容易,如阻塞佇列就是執行緒安全的集合。

執行緒安全的集合

VectorHashTable類提供了執行緒安全的動態陣列和散列表,而ArrayListHashMap卻不是執行緒安全的。

java.util.concurrent包提供了對映表、有序集、佇列的高效實現,如:

  • ConcurrentLinkedQueue:多執行緒安全訪問,無邊界,非阻塞,佇列;

  • ConcurrentHashMap:多執行緒安全訪問,雜湊對映表,初始容量預設16,調整因子預設0.75。

併發的雜湊對映表ConcurrentHashMap

提供原子性的關聯插入putIfAbsent(key, value)和關聯刪除removeIfPresent(key, value)。寫陣列的拷貝CopyOnWriteArrayListCopyOnWriteArraySet是執行緒安全的集合,所有的修改執行緒會對底層陣列進行復制。對於經常被修改的資料列表,使用同步的ArrayList效能勝過CopyOnWriteArrayList

對於執行緒安全的集合,返回的是弱一致性的迭代器:

  • 迭代器不一定能反映出構造後的所有修改;

  • 迭代器不會將同一個值返回兩次;

  • 迭代器不會丟擲ConcurrentModificationException異常。

通常執行緒安全的集合能夠高效的支援大量的讀者和一定數量的寫者,當寫者執行緒數目大於設定值時,後來的寫者執行緒會被暫時阻塞。而對於大多數執行緒安全的集合,size()方法一般無法在常量時間完成,一般需要遍歷整個集合才能確定大小。

同步包裝器

任何集合類使用同步包裝器都會變成執行緒安全的,會將集合的方法使用鎖加以保護,保證執行緒的安全訪問。使用同步包裝器時要確保沒有任何執行緒通過原始的非同步方法訪問資料結構,也可以說確保不存在任何指向原始物件的引用,可以採用下面構造一個集合並立即傳遞給包裝器的方法定義。

List<E> synchArrayList = Collections.synchronizedList(new
ArrayList<E>()); Map<K, V> synchHashMap = Collections.synchronizedMap(new HashMap<K, V>());

當然最好使用java.util.concurrent包中定義的集合,同步包裝器並沒有太多安全和效能上的優勢。

Callable與Future

CallableRunnable類似,都可以封裝一個非同步執行的任務,但是Callable有返回值。Callabele<T>介面是一個引數化的型別,只有一個方法call(),型別引數就是返回值的型別。Future用來儲存非同步計算的結果,用get()方法獲取結果。get()方法的呼叫會被阻塞,直到計算完成。有超時引數的get()方法超時時會丟擲TimeoutException異常。

FutureTask可將Callable轉換成FutureRunnable,實現了兩者的介面。

Callable<Integer> myComputation = new MyComputationCallable();
FutureTask<Integer> task = new FutureTask<Integer>(myComputation);
Thread t = new Thread(task);  // it's a Runnable
t.start();
Integer result = task.get();  // it's a Future

這裡有一個計算指定目錄及其子目錄下與關鍵字匹配的檔案數目的例子,涉及到CallableFutureTaskFuture的使用。

public Integer call() {
    count = 0;
    try {
        File [] files = directory.listFiles();
        List<Future<Integer>> results = new ArrayList<>();

        for (File file : files) {
            if (file.isDirectory()) {
                MatchCounter counter = new MatchCounter(file, keyword);
                FutureTask<Integer> task = new FutureTask<>(counter);
                results.add(task);
                Thread t = new Thread(task);
                t.start();
            } else {
                if (search(file)) {
                    count++;
                }
            }
        }

        for (Future<Integer> result : results) {
            try {
                count += result.get();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    } catch (InterruptedException e) {
        ;
    }
    return count;
}

執行緒池

構建一個新的執行緒是有代價的,涉及到與作業系統的互動。對於程式中需要建立大量生命期很短的執行緒,應該使用執行緒池。執行緒池中的執行緒執行完畢並不會馬上死亡,而是在池中準備為下一個請求提供服務。當然使用執行緒池還可以限制併發執行緒的數目。

需要呼叫執行器Executors的靜態工廠方法來構建執行緒池,下面的方法返回的是ExecutorService介面的ThreadPoolExecutor類的物件。

  • Executors.newCachedThreadPool:執行緒空閒60秒後終止,若有空閒執行緒立即執行任務,若無則建立新執行緒。

  • Executors.newFixedThreadPool:池中執行緒數由引數指定,固定大小,剩餘任務放置在佇列。

使用submit()方法,將Runnable物件或Callable物件提交給執行緒池ExecutorService,任務何時執行由執行緒池決定。呼叫submit()方法,會返回一個Future物件,用來查詢任務狀態或結果。當用完執行緒池時,要記得呼叫shutdown()關閉,會在所有任務執行完後徹底關閉。類似的呼叫shutdownNow,可取消尚未開始的任務並試圖終端正在執行的執行緒。

執行緒池的使用步驟大致如下:

  1. 呼叫Executors類的靜態方法newCachedThreadPool()newFixedThreadPool()

  2. 呼叫submit()提交RunnableCallable物件;

  3. 如果提交Callable物件,就要儲存好返回的Future物件;

  4. 執行緒池用完時,呼叫shutdown()

對於之前提到的計算檔案匹配數的例子,需要產生大量生命期很多的執行緒,可以使用一個執行緒池來執行任務,完整程式碼在這裡

public Integer call() {
    count = 0;
    try {
        File [] files = directory.listFiles();
        List<Future<Integer>> results = new ArrayList<>();
        for (File file : files) {
            if (file.isDirectory()) {
                MatchCounter counter = new MatchCounter(file, keyword, pool);
                Future<Integer> result = pool.submit(counter);
                results.add(result);
            } else {
                if (search(file)) {
                    count++;
                }
            }
        }
        for (Future<Integer> result : results) {
            try {
                count += result.get();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    } catch (InterruptedException e) {
        ;
    }
    return count;
}

Fork-Join框架

對於多執行緒程式,有些應用使用了大量執行緒,但其中大多數都是空閒的。還有些應用需要完成計算密集型任務,Fork-Join框架專門用來支援這類任務。使用Fork-Join框架解決思路大致是分治的思想,採用遞迴計算再合併結果。只需繼承RecursiveTask<T>類,並覆蓋compute()方法。invokeAll()方法接收很多工並阻塞,直到這些任務完成,join()方法將生成結果。

對於問題,統計陣列中滿足某特性的元素個數,使用Fork-Join框架是很合適的。

import java.util.concurrent.*;

public class ForkJoinTest {
    public static void main(String [] args) {
        final int SIZE = 10000000;
        double [] numbers = new double[SIZE];
        for (int i = 0; i < SIZE; i++) {
            numbers[i] = Math.random();
        }
        Counter counter = new Counter(numbers, 0, numbers.length, new Filter() {
            public boolean accept(double x) {
                return x > 0.5;
            }
        });
        ForkJoinPool pool = new ForkJoinPool();
        pool.invoke(counter);
        System.out.println(counter.join());
    }
}

interface Filter {
    boolean accept(double t);
}

class Counter extends RecursiveTask<Integer> {
    private final int THRESHOLD = 1000;
    private double [] values;
    private int from;
    private int to;
    private Filter filter;

    public Counter(double [] values, int from, int to, Filter filter) {
        this.values = values;
        this.from = from;
        this.to = to;
        this.filter = filter;
    }

    public Integer compute() {
        if (to - from < THRESHOLD) {
            int count = 0;
            for (int i = from; i < to; i++) {
                if (filter.accept(values[i])) {
                    count++;
                }
            }
            return count;
        } else {
            int mid = (from + to) / 2;
            Counter first = new Counter(values, from, mid, filter);
            Counter second = new Counter(values, mid, to, filter);
            invokeAll(first, second);
            return first.join() + second.join();
        }
    }
}

另外,Fork-Join框架使用工作密取來平衡可用執行緒的工作負載,比手工多執行緒強多了。

轉:https://segmentfault.com/a/1190000004918145

相關推薦

Java執行執行安全非同步執行

多執行緒併發修改一個數據結構,很容易破壞這個資料結構,如散列表。鎖能夠保護共享資料結構,但選擇執行緒安全的實現更好更容易,如阻塞佇列就是執行緒安全的集合。 執行緒安全的集合 Vector和HashTable類提供了執行緒安全的動態陣列和散列表,而ArrayList和H

Java併發程式設計系列七 正確終止恢復執行

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

java線程線程安全

發生 stack 經典 eat int create 加鎖 情況 zed 線程安全和非線程安全是多線程的經典問題,非線程安全會在多個線程對同一個對象並發訪問時發生。 註意1: 非線程安全的問題存在於實例變量中,如果是方法內部的私有變量,則不存在非線程安全問題。 實例變量是對

#Java&面試--控制執行安全順序執行

Condition類的signal則是喚醒被Condition類使用await作用的那個執行緒,它會有針對性的喚醒執行緒,而不是隨機喚醒一個執行緒,以保證執行緒執行的順序: package com.yzh.job.test; import java.util.c

JAVA執行——經典面試消費者生產者

用wait與notify、notifyAll 實現生產者與消費者 關於多執行緒的生產者與消費者有多種方式實現。目前用學過的wait、notifyAll來實現。程式碼: public class ThreadTest6 { static class

Qt 執行可重入執行安全

Qt 多執行緒之可重入與執行緒安全是本節要介紹的內容。在Qt文件中,術語“可重入”與“執行緒安全”被用來說明一個函式如何用於多執行緒程式。假如一個類的任何函式在此類的多個不同的例項上,可以被多個執行緒同時呼叫,那麼這個類被稱為是“可重入”的。假如不同的執行緒作用在同一個例

執行同步鎖 加鎖和執行停止

一.同步程式碼塊(同步鎖) 寫法: synchronized(鎖){ 上鎖的程式碼 } 當執行緒進入同步程式碼塊 會把鎖拿走 執行程式碼塊中的程式碼 程式碼執行完畢後 會把鎖還回去 如果執行緒遇到同步程式碼塊 發現沒有鎖 將進入等待(有鎖才可進) 鎖的注意:保證所有執行緒使用的是同一個鎖

JAVA中的執行安全執行安全

ArrayList和Vector有什麼區別?HashMap和HashTable有什麼區別?StringBuilder和StringBuffer有什麼區別?這些都是Java面試中常見的基礎問題。面對這樣的問題,回答是:ArrayList是非執行緒安全的,Vector是執行緒

JAVA中的執行安全執行安全理解

執行緒安全性不是一個非真即假的命題。 Vector 的方法都是同步的,並且 Vector 明確地設計為在多執行緒環境中工作。但是它的執行緒安全性是有限制的,即在某些方法之間有狀態依賴(類似地,如果在迭代過程中 Vector 被其他執行緒修改,那麼由 Vector.iterator() 返回的 itera

執行安全執行安全

1、執行緒不共享資料 對同一資源,各個執行緒各自執行一遍,程式碼如下: package com.zzm.th01; /** * 執行緒不共享資料 * Created by ming on 2017/6/15. */ public class th04 extends Thr

(轉)PHP執行安全執行安全的區別:如何選擇用哪一個?

PHP執行緒安全與非執行緒安全的區別:如何選擇用哪一個? 很多時候,我們在做PHP環境配置的時候,很多人都是直接去亂下載PHP版本的,但是他不清楚:從2000年10月20日釋出的第一個Windows版的PHP3.0.17開始的都是執行緒安全的版本,直至5.2.1版本開始有Thread Safe

iOS開發執行間的MachPort通訊執行中的Notification轉發

如題,今天的部落格我們就來記錄一下iOS開發中使用MachPort來實現執行緒間的通訊,然後使用該知識點來轉發子執行緒中所發出的Notification。簡單的說,MachPort的工作方式其實是將NSMachPort的物件新增到一個執行緒所對應的RunLoop中,並給NSMachPort物件設定相應的代理。

PHP的執行安全執行安全

什麼是執行緒安全? Thread Safety means that binary can work in a multithreaded webserver context, such as Apache 2 on Windows. Thread Sa

windows下php執行安全執行安全的版本選擇

Windows下的PHP版本分兩種:執行緒安全版本與非執行緒安全版本。 要論兩者的區別,詳細論說起來比較麻煩,從使用者的角度,記住什麼時候用哪種版本的區別就可以了吧: 1、windows + IIS + FastCGI :使用非執行緒安全版本。

PHP-執行安全執行安全版本的區別

Windows版的PHP從版本5.2.1開始有Thread Safe(執行緒安全)和None Thread Safe(NTS,非執行緒安全)之分,這兩者不同在於何處?到底應該用哪種?這裡做一個簡單的介紹。       從2000年10月20日釋出的第一個Windows版的P

PHP執行安全執行安全的區別

       轉載自:http://koda.iteye.com/blog/662034        Windows版的PHP從版本5.2.1開始有Thread Safe(執行緒安全)和None Thread Safe(NTS,非執行緒安全)之分,這兩者不同在於何處?到

java線程2.線程安全可見性

數據庫連接 其實在 jdbc規範 java多線程 完成 副本 tar 理解 指向 要編寫正確的並發程序,關鍵在於:在訪問共享的可變狀態時需要進行正確的管理 可見性: 同步的另一個重要目的——內存可見性。 我們不僅希望防止某個線程正在使用對象狀態而另一個線程同時在修改狀態

【深入Java虛擬機器】九:類載入及執行子系統的案例實戰

摘自《深入理解 Java 虛擬機器:JVM 高階特性與最佳實踐》(第二版) 概述         在 Class 檔案格式與執行引擎這部分中,使用者的程式能直接影響的內容並不太多,Class 檔案以何種格式儲存,型

JAVA線程先行發生原則

程序 sync 影響 cnblogs 代碼 之間 發生 變量 nal 一、引子     如果java內存模型中所有的有序性都僅僅依靠volatile和synchronized來完成,那麽有一些操作會變得很繁瑣,但我們在編寫java並發代碼時並未感覺到這一點,這是因為java

JAVA線程volatile synchronized 的比較

@override effect process 棧空間 完成 內存可見性 沒有 hash 主從 一,volatile關鍵字的可見性 要想理解volatile關鍵字,得先了解下JAVA的內存模型,Java內存模型的抽象示意圖如下: 從圖中可以看出: ①每個線程都有一個自己的