多執行緒(三)

Kakarotto發表於2018-04-28

一、ThreadLocal

/**
 * ThreadLocal
 * 就是一個Map。key - 》 Thread.getCurrentThread().  value - 》 執行緒需要儲存的變數。
 * ThreadLocal.set(value) -> map.put(Thread.getCurrentThread(), value);
 * ThreadLocal.get() -> map.get(Thread.getCurrentThread());
 * 記憶體問題 : 在併發量高的時候,可能有記憶體溢位。
 * 使用ThreadLocal的時候,一定注意回收資源問題,每個執行緒結束之前,將當前執行緒儲存的執行緒變數一定要刪除 。
 * ThreadLocal.remove();
 */
package concurrent.t05;

import java.util.concurrent.TimeUnit;

public class Test_01 {

	volatile static String name = "zhangsan";
	static ThreadLocal<String> tl = new ThreadLocal<>();
	
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					TimeUnit.SECONDS.sleep(2);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(name);
				System.out.println(tl.get());
			}
		}).start();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				name = "lisi";
				tl.set("wangwu");
			}
		}).start();
	}
	
}
複製程式碼

結果:

lisi
null
複製程式碼

二、併發包

1、ConcurrentHashMap

/**
 * 併發容器 - ConcurrentMap
 */
package concurrent.t06;

import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CountDownLatch;

public class Test_01_ConcurrentMap {
	
	public static void main(String[] args) {
//		final Map<String, String> map = new Hashtable<>();
//		final Map<String, String> map = new ConcurrentHashMap<>();
		final Map<String, String> map = new ConcurrentSkipListMap<>();
		final Random r = new Random();
		Thread[] array = new Thread[100];
		final CountDownLatch latch = new CountDownLatch(array.length);
		
		long begin = System.currentTimeMillis();
		for(int i = 0; i < array.length; i++){
			array[i] = new Thread(new Runnable() {
				@Override
				public void run() {
					for(int j = 0; j < 10000; j++){
						map.put("key"+r.nextInt(100000), "value"+r.nextInt(100000));
					}
					latch.countDown();
				}
			});
		}
		for(Thread t : array){
			t.start();
		}
		try {
			latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("執行時間為 : " + (end-begin) + "毫秒!");
	}

}
複製程式碼

結果:ConcurrentHashMap併發包效率比較高,執行緒安全

2、CopyOnWriteArrayList

/**
 * 併發容器 - CopyOnWriteList
 */
package concurrent.t06;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;

public class Test_02_CopyOnWriteList {
	
	public static void main(String[] args) {
		// final List<String> list = new ArrayList<>();
		// final List<String> list = new Vector<>();
		final List<String> list = new CopyOnWriteArrayList<>();
		final Random r = new Random();
		Thread[] array = new Thread[100];
		final CountDownLatch latch = new CountDownLatch(array.length);
		
		long begin = System.currentTimeMillis();
		for(int i = 0; i < array.length; i++){
			array[i] = new Thread(new Runnable() {
				@Override
				public void run() {
					for(int j = 0; j < 1000; j++){
						list.add("value" + r.nextInt(100000));
					}
					latch.countDown();
				}
			});
		}
		for(Thread t : array){
			t.start();
		}
		try {
			latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		long end = System.currentTimeMillis();
		System.out.println("執行時間為 : " + (end-begin) + "毫秒!");
		System.out.println("List.size() : " + list.size());
	}

}
複製程式碼

結果:

執行時間為 : 4550毫秒!
List.size() : 100000
複製程式碼

3、ConcurrentLinkedQueue

一個基於連結節點的無界執行緒安全佇列。此佇列按照 FIFO(先進先出)原則對元素進行排序。佇列的頭部 是佇列中時間最長的元素。佇列的尾部 是佇列中時間最短的元素。 新的元素插入到佇列的尾部,佇列獲取操作從佇列頭部獲得元素。當多個執行緒共享訪問一個公共 collection 時,ConcurrentLinkedQueue是一個恰當的選擇。此佇列不允許使用 null 元素。

多執行緒(三)
ConcurrentLinkedQueue由head節點和tair節點組成,每個節點(Node)由節點元素(item)和指向下一個節點的引用(next)組成,節點與節點之間就是通過這個next關聯起來,從而組成一張連結串列結構的佇列。預設情況下head節點儲存的元素為空,tair節點等於head節點。

private transient volatile Node<e> tail = head;
複製程式碼
  • offer和poll

offer(E e)
將指定元素插入此佇列的尾部。

poll()
獲取並移除此佇列的頭,如果此佇列為空,則返回 null。

public static void main(String[] args) {
    ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
    queue.offer("哈哈哈");
    System.out.println("offer後,佇列是否空?" + queue.isEmpty());
    System.out.println("從佇列中poll:" + queue.poll());
    System.out.println("pool後,佇列是否空?" + queue.isEmpty());
}
複製程式碼

offer是往佇列新增元素,poll是從佇列取出元素並且刪除該元素
執行結果:

offer後,佇列是否空?false
從佇列中poll:哈哈哈
pool後,佇列是否空?true
複製程式碼

ConcurrentLinkedQueue中的add() 和 offer()完全一樣,都是往佇列尾部新增元素

peek()
獲取但不移除此佇列的頭;如果此佇列為空,則返回 null

public static void main(String[] args) {
    ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
    queue.offer("哈哈哈");
    System.out.println("offer後,佇列是否空?" + queue.isEmpty());
    System.out.println("從佇列中peek:" + queue.peek());
    System.out.println("從佇列中peek:" + queue.peek());
    System.out.println("從佇列中peek:" + queue.peek());
    System.out.println("pool後,佇列是否空?" + queue.isEmpty());
}
複製程式碼

執行結果:

offer後,佇列是否空?false
從佇列中peek:哈哈哈
從佇列中peek:哈哈哈
從佇列中peek:哈哈哈
pool後,佇列是否空?false
複製程式碼

remove(Object o)
從佇列中移除指定元素的單個例項(如果存在)

public static void main(String[] args) {
    ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
    queue.offer("哈哈哈");
    System.out.println("offer後,佇列是否空?" + queue.isEmpty());
    System.out.println("從佇列中remove已存在元素 :" + queue.remove("哈哈哈"));
    System.out.println("從佇列中remove不存在元素:" + queue.remove("123"));
    System.out.println("remove後,佇列是否空?" + queue.isEmpty());
}
複製程式碼

remove一個已存在元素,會返回true,remove不存在元素,返回false
執行結果:

offer後,佇列是否空?false
從佇列中remove已存在元素 :true
從佇列中remove不存在元素:false
remove後,佇列是否空?true
複製程式碼

size or isEmpty
size()
返回此佇列中的元素數量
注意:

如果此佇列包含的元素數大於 Integer.MAX_VALUE,則返回 Integer.MAX_VALUE。
需要小心的是,與大多數 collection 不同,此方法不是 一個固定時間操作。由於這些佇列的非同步特性,確定當前的元素數需要進行一次花費 O(n) 時間的遍歷。
所以在需要判斷佇列是否為空時,儘量不要用 queue.size()>0,而是用 !queue.isEmpty()
複製程式碼

isEmpty()的效率要高很多

4、LinkedBlockingQueue

/**
 * 併發容器 - LinkedBlockingQueue
 *  阻塞容器。
 *  put & take - 自動阻塞。
 *  put自動阻塞, 佇列容量滿後,自動阻塞
 *  take自動阻塞方法, 佇列容量為0後,自動阻塞。
 */
package concurrent.t06;

import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class Test_04_LinkedBlockingQueue {
	
	final BlockingQueue<String> queue = new LinkedBlockingQueue<>();
	final Random r = new Random();
	
	public static void main(String[] args) {
		final Test_04_LinkedBlockingQueue t = new Test_04_LinkedBlockingQueue();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true){
					try {
						t.queue.put("value"+t.r.nextInt(1000));
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}, "producer").start();
		
		for(int i = 0; i < 3; i++){
			new Thread(new Runnable() {
				@Override
				public void run() {
					while(true){
						try {
							System.out.println(Thread.currentThread().getName() + 
									" - " + t.queue.take());
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}, "consumer"+i).start();
		}
	}

}
複製程式碼

結果:

consumer0 - value221
consumer1 - value291
consumer0 - value386
consumer2 - value56
consumer1 - value336
consumer0 - value522
consumer2 - value731
consumer1 - value757
consumer0 - value497
consumer2 - value696
consumer1 - value589
複製程式碼

5、ArrayBlockingQueue

/**
 * 併發容器 - ArrayBlockingQueue
 *  有界容器。add方法當容量不足的時候有阻塞能力
 *  put方法在容量不足的時候阻塞
 *  offer方法
 *  單引數offer方法,不阻塞,容量不足的時候,返回false,當前新增資料操作放棄
 *  三引數offer方法(offer(value,times,timeunit)),容量不足的時候阻塞times時長(單位為timeunit),
 *  如果在阻塞時長內,有容量空閒,新增資料返回true,如果阻塞時長範圍內無容量空閒,放棄新增資料返回false
 */
package concurrent.t06;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class Test_05_ArrayBlockingQueue {
	
	final BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
	
	public static void main(String[] args) {
		final Test_05_ArrayBlockingQueue t = new Test_05_ArrayBlockingQueue();
		
		for(int i = 0; i < 5; i++){
			System.out.println("add method : " + t.queue.add("value"+i));
			/*try {
				t.queue.put("put"+i);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("put method : " + i);*/
			// System.out.println("offer method : " + t.queue.offer("value"+i));
//			try {
//				System.out.println("offer method : " + 
//							t.queue.offer("value"+i, 1, TimeUnit.SECONDS));
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
		}
		
		System.out.println(t.queue);
	}

}
複製程式碼

結果:

add method : true
add method : true
Exception in thread "main" java.lang.IllegalStateException: Queue fulladd method : true

	at java.util.AbstractQueue.add(Unknown Source)
	at java.util.concurrent.ArrayBlockingQueue.add(Unknown Source)
	at concurrent.t06.Test_05_ArrayBlockingQueue.main(Test_05_ArrayBlockingQueue.java:22)
複製程式碼

6、DelayQueue

/**
 * 併發容器 - DelayQueue
 *  無界容器。
 * 常用於計劃任務,如定時傳送郵件
 */
package concurrent.t06;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class Test_06_DelayQueue {
	
	static BlockingQueue<MyTask_06> queue = new DelayQueue<>();
	
	public static void main(String[] args) throws InterruptedException {
		long value = System.currentTimeMillis();
		MyTask_06 task1 = new MyTask_06(value + 2000);
		MyTask_06 task2 = new MyTask_06(value + 1000);
		MyTask_06 task3 = new MyTask_06(value + 3000);
		MyTask_06 task4 = new MyTask_06(value + 2500);
		MyTask_06 task5 = new MyTask_06(value + 1500);
		
		queue.put(task1);
		queue.put(task2);
		queue.put(task3);
		queue.put(task4);
		queue.put(task5);
		
		System.out.println(queue);
		System.out.println(value);
		for(int i = 0; i < 5; i++){
			System.out.println(queue.take());
		}
	}

}

class MyTask_06 implements Delayed {
	
	private long compareValue;
	
	public MyTask_06(long compareValue){
		this.compareValue = compareValue;
	}

	/**
	 * 比較大小。自動實現升序
	 * 建議和getDelay方法配合完成。
	 * 如果在DelayQueue是需要按時間完成的計劃任務,必須配合getDelay方法完成。
	 */
	@Override
	public int compareTo(Delayed o) {
		return (int)(this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
	}

	/**
	 * 獲取計劃時長的方法。
	 * 根據引數TimeUnit來決定,如何返回結果值。
	 */
	@Override
	public long getDelay(TimeUnit unit) {
		return unit.convert(compareValue - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
	}
	
	@Override
	public String toString(){
		return "Task compare value is : " + this.compareValue;
	}
	
}
複製程式碼

結果:

[Task compare value is : 1524846737601, Task compare value is : 1524846738101, Task compare value is : 1524846739601, Task compare value is : 1524846739101, Task compare value is : 1524846738601]
1524846736601
Task compare value is : 1524846737601
Task compare value is : 1524846738101
Task compare value is : 1524846738601
Task compare value is : 1524846739101
Task compare value is : 1524846739601
複製程式碼

7、LinkedTransferQueue

/**
 * 併發容器 - LinkedTransferQueue
 *  轉移佇列
 *  add - 佇列會儲存資料,不做阻塞等待。
 *  transfer - 是TransferQueue的特有方法。必須有消費者(take()方法的呼叫者)。
 *   如果沒有任意執行緒消費資料,transfer方法阻塞。一般用於處理即時訊息。
 */
package concurrent.t06;

import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TransferQueue;

public class Test_07_TransferQueue {
	
	TransferQueue<String> queue = new LinkedTransferQueue<>();
	
	public static void main(String[] args) {
		final Test_07_TransferQueue t = new Test_07_TransferQueue();
		
		/*new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					System.out.println(Thread.currentThread().getName() + " thread begin " );
					System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "output thread").start();
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		try {
			t.queue.transfer("test string");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}*/
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				try {
					t.queue.transfer("test string");
					// t.queue.add("test string");
					System.out.println("add ok");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					System.out.println(Thread.currentThread().getName() + " thread begin " );
					System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "output thread").start();
		
	}

}
複製程式碼

結果:

output thread thread begin 
output thread - test string
add ok
複製程式碼

8、SynchronousQueue

同步佇列,是一個容量為 0 的佇列。是一個特殊的 TransferQueue。必須有消費者消費。

add方法,無阻塞。若沒有消費執行緒阻塞等待資料,則丟擲異常

put方法,有阻塞,若沒有消費執行緒阻塞等待資料,則阻塞。

/**
 * 併發容器 - SynchronousQueue
 */
package concurrent.t06;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class Test_08_SynchronusQueue {
	
	BlockingQueue<String> queue = new SynchronousQueue<>();
	
	public static void main(String[] args) {
		final Test_08_SynchronusQueue t = new Test_08_SynchronusQueue();
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					System.out.println(Thread.currentThread().getName() + " thread begin " );
					try {
						TimeUnit.SECONDS.sleep(2);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + " - " + t.queue.take());
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "output thread").start();
		
		/*try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}*/
		// t.queue.add("test add");
		try {
			t.queue.put("test put");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(Thread.currentThread().getName() + " queue size : " + t.queue.size());
	}

}
複製程式碼

結果:

output thread thread begin 
output thread - test put
main queue size : 0
複製程式碼

相關文章