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());
		}
	}
}

```

好了, 啰里啰嗦,说了一大通,在开发中如果不确定使用哪一种,可以根据并发容器各自的特点选择适合自己的应用场景。 当然还有系统封装好的并发容器, 使用起来可靠性更好,毕竟经过了长时间的市场测试和磨砺。

东西比较多,如果有什么不对的,请批评指正。 这篇就先说到这里,下篇我们再见。

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