Java核心技术 卷1 多线程----线程安全的集合(4)
如果多線程要并發的修改一個數據結構,例如散列表,那么很容易會破壞這個數據結構。一個線程可能要開始向表中插入一個新元素。假定在調整散列表各個桶之間的鏈接關系的過程中,被剝奪了控制權。如果另一個線程也開始遍歷同一個鏈表,可能使用無效的鏈接并造成混亂,會拋出異常或者陷入死循環。
可以通過提供鎖來保護共享數據結構,但是選擇線程安全的實現作為替代可能更容易些。上一篇討論的阻塞隊列就是線程安全的集合。接下來討論java類庫提供的另外一些線程安全的集合。
高效的映射表、集合和隊列
java.util.concurrent包提供了映射表、有序集合和隊列的高效實現:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue。
java.util.concurrent.ConcurrentLinkedQueue<E>- ConcurrentLinkedQueue<E>() 構造一個可以被多線程安全訪問的無邊界非阻塞的隊列。
- ConcurrentSkipListSet<E>(Comparateor<? super E> comp) ? ?構造一個可以被多線程安全訪問的有序集
- ConcurrentHashMap<K,V>()
- ConcurrentHashMap<K,V>(int initialCapacity)
- ConcurrentHashMap<K,V>(int?initialCapacity,float loadFactor,int concurrencyLevel)
參數:initialCapacity 集合的初始容量。默認值16
? loadFactor ? ? ?控制調整:如果每一個桶的平均負載超過這個因子,表的大小會被重新調整。默認值為0.75.
concurrencyLevel 并發寫者結束 的估計數目。
- ConcurrentSkipListMap<K,V>
- ConcurrentSkipListSet<K,V>(Comparator<? super k) comp)
構造一個可以被多線程安全訪問的有序的映象表。第一個構造器要求鍵實現Comparable接口。
- V putIfAbsent(K key,V value)
如果該鍵沒有在映像表中出現,則將給定的值同給定的鍵關聯起來,并返回null。否則返回與該鍵關鍵的現有值。
- boolean remove(K key,V value)
如果給定的鍵與給定的值關聯,刪除給定的鍵與值并返回真。否則,返回false.
- boolean replace(K key,V oldValue,V newValue)
如果給定的鍵當前與oldvalue相關聯,用它與newValue關聯。否則返回false
寫數組的拷貝
CopyOnWriteArrayList和CopyOnWriteArraySet是線程安全的集合。其中所有的修改線程對底層數組進行復制。如果在集合上進行迭代的線程數超過修改線程數,這樣的安排是很有用的。當構建一個迭代器的時候,它包含一個對當前數組的引用。如果數組后來被修改了,迭代器仍然引用舊數組,但是,集合的數組已經被替換了。因為舊的迭代器擁有一致的視圖,訪問無須任何同步開銷。
Callable與Future
Runnable封裝了一個異步的任務,可以把它想像成為一個沒有參數和返回值的異步方法。Callable與Runnable類似,但是有返回值。Callable接口是一個參數化的類型,只有一個方法call。
public interface Callable<V> { V call() throws Exception; }類型參數是返回值的類型。例如:Callable<Integer>表示一個最終返回Integer對象的異步計算。
Future保存異步計算的結果。可以啟動一個計算,將Future對象交給某個線程,然后忘掉它。Future對象的所有者在結果計算 好之后就可以獲得它。
Future接口具有下面的方法:
public interface Future<V> {V get() throws ...;V get(long timeout,TimeUnit unit) throws ...;void cancel(boolean mayInterrupt);boolean isCancelled();boolean isDone(); }第一個get方法的調用被阻塞,直到計算完成。如果在計算完成之前,第二個方法的調用超時,拋出一個TimeoutException異常。如果運行該計算的線程被中斷,兩個方法都將拋出InterruptedException。 如果計算完成,那么get方法立即返回。
如果計算還在進行,isDone方法返回false;如果完成了,則么回true.
可以用cancel方法取消該計算。如果計算還沒有開始,它被取消且不再開始。如果計算處于運行中,那么如果mayInterrupt參數為true,它就被中斷。
FutureTask包裝器是一種非常便利的機制,可以將Callable轉換成Future轉換成Future和Runnable,它同時實現二者的接口。
以下這個例子與上一篇尋找包含指定關鍵字的文件的例子相似。然而,現在我們僅僅計算匹配的文件數目。因此,我們有了一個需要長時間運行的任務,它產生一個數例,一個Callable<Integer>的例子。
class MatchCouner implements Callable<Integer> {public MatchCounter(Fille directory,String keyWord){...}public Integer call(){...} //返回匹配文件的數 }然后我們利用MatchCounter創建一個FutureTask對象,并用來啟動一個線程。
FutureTask<Integer> task =new FutureTask<Integer>(counter); Thread t=new Thread(task); t.start();最后,打印結果:?
System.out.println(task.get()+" mathcing files.");當然對get的調用會發生阻塞,直到有可獲得的結果為止。
在call方法內部,使用相同的遞歸機制。對于每一個子目錄,產生一個新的MatchCounter并為它啟動一個線程。把FutureTask對象隱藏在ArrayList<Future<Integer>>中。最后所有結果加起來:
for(Future<Integer> result:results)count+=result.get()每一次對get的調用都會發生阻塞直到結果可獲得為止。當然線程是并行運行的,因此,很可能在大致相同的時候所有的結果都可獲得。完整代碼如下:
1 package test.Future; 2 3 import java.io.File; 4 import java.util.Scanner; 5 import java.util.concurrent.ExecutionException; 6 import java.util.concurrent.FutureTask; 7 8 /** 9 * Created by Administrator on 2017/11/23. 10 */ 11 public class FutureTest { 12 public static void main(String[] args) { 13 Scanner in =new Scanner(System.in); 14 System.out.print("輸入查詢路徑:"); 15 String directory=in.nextLine(); 16 System.out.print("輸入關鍵字:"); 17 String keyword=in.nextLine(); 18 19 MatchCounter counter=new MatchCounter(new File(directory),keyword); 20 FutureTask<Integer> task=new FutureTask<>(counter); 21 Thread t=new Thread(task); 22 t.start(); 23 try { 24 System.out.println(task.get()+" 匹配文件"); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } catch (ExecutionException e) { 28 e.printStackTrace(); 29 } 30 31 } 32 } View Code 1 package test.Future; 2 3 import java.io.*; 4 import java.util.ArrayList; 5 import java.util.List; 6 import java.util.Scanner; 7 import java.util.concurrent.Callable; 8 import java.util.concurrent.ExecutionException; 9 import java.util.concurrent.Future; 10 import java.util.concurrent.FutureTask; 11 12 /** 13 * 此任務對包含給定關鍵字的目錄和子目錄中的文件進行計數 14 */ 15 public class MatchCounter implements Callable<Integer> { 16 private File directory; 17 private String keyword; 18 private int count; 19 20 /** 21 * @param directory 開始搜索目錄 22 * @param keyword 尋找關鍵字 23 */ 24 public MatchCounter(File directory, String keyword) { 25 this.directory = directory; 26 this.keyword = keyword; 27 } 28 29 @Override 30 public Integer call() throws Exception { 31 count = 0; 32 try { 33 File[] files = directory.listFiles(); 34 if (files == null) { 35 return count; 36 } 37 List<Future<Integer>> results = new ArrayList<>(); 38 for (File file : files 39 ) { 40 if (file.isDirectory()) { 41 MatchCounter counter = new MatchCounter(file, keyword); 42 FutureTask<Integer> task = new FutureTask<>(counter); 43 results.add(task); 44 Thread t = new Thread(task); 45 t.start(); 46 } else { 47 if (search(file)) { 48 count++; 49 } 50 } 51 for (Future<Integer> result : results) { 52 try { 53 count += result.get(); 54 } catch (ExecutionException e) { 55 e.printStackTrace(); 56 } 57 } 58 } 59 } catch (InterruptedException e) { 60 } 61 return count; 62 } 63 64 /** 65 * 搜索一個給定關鍵字的文件 66 * 67 * @param file 68 * @return 69 */ 70 public boolean search(File file) { 71 try{ 72 try(Scanner in= new Scanner(file,"gbk")){ 73 boolean found=false; 74 while (!found&&in.hasNextLine()){ 75 String line=in.nextLine(); 76 if (line.contains(keyword)){ 77 found=true; 78 System.out.println(file.toString()); 79 } 80 } 81 return found; 82 } 83 } 84 catch (IOException e){ 85 return false; 86 } 87 } 88 } View Code java.util.concurrent.Callable<V>?
- ?V call() 運行將產生結果的任務
- ?V get()
- ?V get(long time,TimeUnit unit)
獲取結果,如果沒有結果可用,則阻塞直到得到結果超過指定的時間為止。如果不成功,第二個方法會拋出TimeoutException異常。
- boolean cancel(boolean mayInterrupt)
嘗試取消這一任務的運行。如果任務已經開始,并且mayInterrupt參數值為true,它就會被中斷如果成功執行取消操作,返回true。
- boolean isCancelled() 如果任務在完成前被取消了,則返回true。
- boolean isDone() 如果任務結束,無論是正常結束、中途取消或發生異常,都返回true。
- FutureTask(Callable<V> task)
- FutureTask(Runnable task,V result) 構造一個既是Future<V>又是Runnable的對象。
?
轉載于:https://www.cnblogs.com/gousheng107/p/7885681.html
總結
以上是生活随笔為你收集整理的Java核心技术 卷1 多线程----线程安全的集合(4)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 土豆是什么社交软件?
- 下一篇: Java中的注解是如何工作的?