多线程(三)

234 阅读8分钟

一、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