Java高併發系列4-併發容器

Java高併發系列4-併發容器

接上一篇Java高併發系列3-再寫單例我們繼續,

併發容器在高併發中有這舉足輕重的地步,這一篇我們主要看併發容器。

1、併發List

在List下中有ArrayList 、LinkedList 、Vector 三種數據結構,其中Vector屬於線程安全的。
  在List下還有CopyOnWriteArrayList類實現的List接口,它也是線程安全的。

CopyOnWriteArrayList與Vector進行對比:
     (1)鎖的位置
      CopyOnWriteArrayList的實現是在讀操作中去除鎖,而寫中有鎖並且多了複製操作。
      Vector在讀操作和寫操作中都添加了鎖。
    (2)速度比較
      CopyOnWriteArrayList因爲在讀操作中去除了鎖,所以其速度比Vector快。但是CopyOnWriteArrayList中多了複製操作,所以其寫的速度要比Vect慢。

個人推薦:在讀特別多寫少時用CopyOnWriteArrayList , 如果讀少寫多時,用Vector
    
  看一條程序
  
  ```
public class CopyOnWriteList {
public static void main(String[] args) {
List lists =
//new ArrayList<>(); //這個會出併發問題!
//new Vector();
new CopyOnWriteArrayList<>(); /// 寫的情況少,讀的情況特別多。
Random r = new Random();
Thread[] ths = new Thread[100];

    		for(int i=0; i<ths.length; i++) {
    			Runnable task = new Runnable() {
    	
    				@Override
    				public void run() {
    					for(int i=0; i<1000; i++) lists.add("a" + r.nextInt(10000));
    				}
    				
    			};
    			ths[i] = new Thread(task);
    		}
    		
    		
    		runAndComputeTime(ths);
    		
    		System.out.println(lists.size());
    	}
    	
    	static void runAndComputeTime(Thread[] ths) {
    		long s1 = System.currentTimeMillis();
    		Arrays.asList(ths).forEach(t->t.start());
    		Arrays.asList(ths).forEach(t->{
    			try {
    				t.join();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		});
    		long s2 = System.currentTimeMillis();
    		System.out.println(s2 - s1);
    		
    	}
    }

```
  
  當然也可以使用Collections.sync xxx 進行封裝加鎖。

2、併發Set

與List類似,Set也有一個CopyOnWriteArraySet,它實現了Set接口,線程安全。

CopyOnWriteArraySet,其內部實現完全依賴於CopyOnWriteArrayList,所以CopyOnWriteArrayList所具有的特性,CopyOnWriteArraySet全具有。

個人推薦:在讀多寫少時,用CopyOnWriteArraySet。

3、併發Map

看一條程序,

```
public class ConcurrentMap {
	public static void main(String[] args) {
		//Map<String, String> map = new ConcurrentHashMap<>();
		Map<String, String> map = new ConcurrentSkipListMap<>(); //高併發並且排序
		
		//Map<String, String> map = new Hashtable<>();
		//Map<String, String> map = new HashMap<>(); //Collections.synchronizedXXX
		//TreeMap
		Random r = new Random();
		Thread[] ths = new Thread[100];
		CountDownLatch latch = new CountDownLatch(ths.length);
		long start = System.currentTimeMillis();
		for(int i=0; i<ths.length; i++) {
			ths[i] = new Thread(()->{
				for(int j=0; j<10000; j++) map.put("a" + r.nextInt(100000), "a" + r.nextInt(100000));
				latch.countDown();
			});
		}
		
		Arrays.asList(ths).forEach(t->t.start());
		try {
			latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		long end = System.currentTimeMillis();
		System.out.println(end - start);
	}
}

```

運行結果就不粘貼了,
  
   Map下有HashMap、HashTable(子類LinkedHashMap)、TreeMap、ConcurrentHashMap。其中線程安全的是HashTable和ConcurrentHashMap, ConcurrentSkipListMap 。
  
  ConcurrentHashMap與HashTable進行對比:
  ConcurrentHashMap 的get()中不加鎖,put()中又使用減少鎖粒度的方式來進行同步的,而不是像HashTable使用synchronized簡單的進行同步,所以其效率比HashTable高.
  
  ConcurrentSkipListMap 的具體原理 參考 https://blog.csdn.net/sunxianghuang/article/details/52221913
    
  鎖粒度:
  
  拿ConcurrentHashMap來說,他不是將整個HashMap進行加鎖,而是將HashMap分成16段 ,需要put()操作時,根據其hashcode獲取
  該段,對該段進行加特定的鎖,其他段可以被其他線程繼續使用加鎖。 所以實現上是ConcurrentHashMap 的鎖的顆粒度更細, 從而更高效 並不是沒有上鎖。

個人推薦:如果不考慮安全性,使用HashMap。考慮安全性使用ConcurrentHashMap, 如果併發量比較大 並且要求是排好序的使用ConcurrentSkipListMap 。

4、併發Queue

看一條程序

```

public class ConcurrentQueue {
	public static void main(String[] args) {
		Queue<String> strs = new ConcurrentLinkedQueue<>();
		
		for(int i=0; i<10; i++) {
		  /// offer 添加到隊尾 返回添加結果 ,是否成功。 
			strs.offer("a" + i);  //add  添加沒結果
		}
		
		System.out.println(strs);
		
		System.out.println(strs.size());
		
		System.out.println(strs.poll());  /// 讀取隊列頭部,並且移除 
		System.out.println(strs.size());
		
		System.out.println(strs.peek()); //讀取隊列頭部,不移除 
		System.out.println(strs.size());
		
	}
}

```

再看一條程序

```

public class T05_LinkedBlockingQueue {

	static BlockingQueue<String> strs = new LinkedBlockingQueue<>();

	static Random r = new Random();

	public static void main(String[] args) {
	    /// 生產者
		new Thread(() -> {
			for (int i = 0; i < 100; i++) {
				try {
					strs.put("a" + i); //如果滿了,就會等待
					TimeUnit.MILLISECONDS.sleep(r.nextInt(1000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "p1").start();
        /// 消費者
		for (int i = 0; i < 5; i++) {
			new Thread(() -> {
				for (;;) {
					try {
						System.out.println(Thread.currentThread().getName() + " take -" + strs.take()); //如果空了,就會等待
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "c" + i).start();

		}
	}
}

```

再看一條

```

public class ArrayBlockingQueue {
    /// 有界阻塞隊列 ,容量爲10 
	static BlockingQueue<String> strs = new ArrayBlockingQueue<>(10);

	static Random r = new Random();

	public static void main(String[] args) throws InterruptedException {
		for (int i = 0; i < 10; i++) {
			strs.put("a" + i);
		}
		
		strs.put("aaa"); //滿了就會等待,程序阻塞 
		//strs.add("aaa");
		//strs.offer("aaa");
		//strs.offer("aaa", 1, TimeUnit.SECONDS);
		
		System.out.println(strs);
	}
}

```

併發隊列有兩類,一類支持高併發的ConcurrentLinkedQueue,另一類是阻塞隊列BlockingQueue。

詳解:
    ConcurrentLinkedQueue採用的是無鎖的方式,所以其性能在高併發中很好。
    
    BlockingQueue採用的是生產者消費者模式的方式進行加鎖。
    
    Blocking的具體實現有ArrayBlockingQueue 有界隊列 和LinkedBlockingQueue 無界隊列
    
  再來一條程序
  我們來看一條TransferQueue的測試

    public class T08_TransferQueue {
    	public static void main(String[] args) throws InterruptedException {
    		LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();
    		/*
    		new Thread(() -> {
    			try {
    				System.out.println(strs.take());
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}).start();*/
    		// strs.transfer("aaa");
    		
    		strs.put("aaa");
    		new Thread(() -> {
    			try {
    				System.out.println(strs.take());
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}).start();
    	}
    }

再來個SynchronousQueue測試

        public class SynchronusQueue { //容量爲0
        	public static void main(String[] args) throws InterruptedException {
        		BlockingQueue<String> strs = new SynchronousQueue<>();
        		
        		new Thread(()->{
        			try {
        				System.out.println(strs.take());
        			} catch (InterruptedException e) {
        				e.printStackTrace();
        			}
        		}).start();
        		
        		strs.put("aaa"); //阻塞等待消費者消費
        		//strs.add("aaa");
        		System.out.println(strs.size());
        	}
        }
  

TransferQueue 和 SynchronusQueue 是用於高併發 ,需要及時轉發的隊列, 隊列的容量爲0 。 transfer , put

個人推薦:如果需要高併發下有高性能,使用ConcurrentLinkedQueue。如果想要實現數據在多線程中共享,使用BlockingQueue。

5、併發Dueue(雙端隊列)

Dueue的具體實現有LinkedBlockingDueue。Dueue與Queue相比,Dueue繼承了Queue,所以它的功能更 多。但是LinkedBlockingDueue的性能遠遠低於LinkedBlockingQueue,更低於ConcurrenLinkedQueue。

6、定時隊列 DelayQueue執行定時任務

看一條程序

```
public class DelayQueue {

	static BlockingQueue<MyTask> tasks = new DelayQueue<>();

	static Random r = new Random();
	
	static class MyTask implements Delayed {
		long runningTime;
		
		MyTask(long rt) {
			this.runningTime = rt;
		}

		@Override
		public int compareTo(Delayed o) {
			if(this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS))
				return -1;
			else if(this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) 
				return 1;
			else 
				return 0;
		}

		@Override
		public long getDelay(TimeUnit unit) {
			
			return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
		}
		@Override
		public String toString() {
			return "" + runningTime;
		}
	}

	public static void main(String[] args) throws InterruptedException {
		long now = System.currentTimeMillis();
		MyTask t1 = new MyTask(now + 1000);
		MyTask t2 = new MyTask(now + 2000);
		MyTask t3 = new MyTask(now + 1500);
		MyTask t4 = new MyTask(now + 2500);
		MyTask t5 = new MyTask(now + 500);
		
		tasks.put(t1);
		tasks.put(t2);
		tasks.put(t3);
		tasks.put(t4);
		tasks.put(t5);
		
		System.out.println(tasks);
		
		for(int i=0; i<5; i++) {
			System.out.println(tasks.take());
		}
	}
}

```

好了, 囉裏囉嗦,說了一大通,在開發中如果不確定使用哪一種,可以根據併發容器各自的特點選擇適合自己的應用場景。 當然還有系統封裝好的併發容器, 使用起來可靠性更好,畢竟經過了長時間的市場測試和磨礪。

東西比較多,如果有什麼不對的,請批評指正。 這篇就先說到這裏,下篇我們再見。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章