Java高并发系列4-并发容器
Java高并发系列4-并发容器接上一篇Java高并发系列3-再写单例我们继续,并发容器在高并发中有这举足轻重的地步,这一篇我们主要看并发容器。1、并发List在List下中有ArrayList 、LinkedList 、Vector 三种数据结构,其中Vector属于线程安全的。 在List下还有CopyOnWriteArrayList类实现的List接口,它也是线程安全的。Copy...
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());
}
}
}
```
好了, 啰里啰嗦,说了一大通,在开发中如果不确定使用哪一种,可以根据并发容器各自的特点选择适合自己的应用场景。 当然还有系统封装好的并发容器, 使用起来可靠性更好,毕竟经过了长时间的市场测试和磨砺。
东西比较多,如果有什么不对的,请批评指正。 这篇就先说到这里,下篇我们再见。
更多推荐
所有评论(0)