天天看点

并发编程4-容器

打印如下:

因为并发带来了内部错误。

而对于并发容器来讲比如vector和collections.synchronizedlist()就不会出现这个问题。

vector是用方法锁来实现的, 而后者是用同步块来实现的,因此后者的效率较高。

同步容器的单个方法都是安全的,比如上面的那个例子改为使用同步容器:

会打印2000.不会出现异常

但是通常对于容器的操作还会有很多复合操作,比如迭代、缺少才加入等操作,还是会出现问题,这时候需要加入额外的锁。

复合操作:

该代码会抛出:

exception in thread "main" java.util.concurrentmodificationexception

因为遍历的时候会实时检查集合的数量是否发生变化,如果有另外一个线程修改了集合数量则会抛出这个异常。

如果放开代码中的同步块,则不会再抛出异常了。

为了解决这个问题,除了使用加锁的方式外,还可以在遍历之前进行拷贝。

对于这些复合操作,jdk提供了许多类库,提供了比客户端加锁更好的并发性和可伸缩性。

concurrenthashmap

提供了putifabsent等方法提供了一些常用复合操作的并发安全方法。

其实现的机制使用了分离锁, 先hash key到每一个桶上,然后对单独的桶加锁,这样就能够把锁的消耗分解得很小。

同样提供了很多常用复合操作的并发安全方法。

其实现的机制可以看看如下两个方法:

这样所有的可能修改集合的方法都是加了锁的,在修改的时候创建了新的集合,永远不会修改老的集合。

而不会修改集合的地方,比如遍历集合是直接返回了一个当前的数组引用,这个引用不会被修改,因为修改行为会创建新的数组来给引用赋值。

这样很适用于写少读多的情况。

前面说过使用wait和notifyall来实现生产者消费者模式。 这里我们有更好的集合可以使用 blockingqueue

这里如果使用非安全的队列,出了无法实现阻塞效果外,还会造成,多线程写丢失,内部状态不一致,甚至抛出边界异常等等

对于队列,还提供了如下的方法:

add offer  put  添加一个元素,  如果满了抛出异常,返回false, 阻塞(仅bockingqueue支持)

remove poll take 取第一个元素并删除 , 如果集合为空,抛出异常,返回null ,阻塞(仅bockingqueue支持)

element  peek 返回头元素,  如果为空  抛出异常, 返回null.

出了上面这种外jdk还根据需求提供了别的,比如priorityqueue 根据compare方法来决定取出顺序的队列

deque和blockingdeque双向队列,每个消费者有自己的双端队列,自己的队列完成之后会尝试去消费其他的队列。