java复习(五)

1.java包中常用的同步控制工具

1.1Atomic

在多线程或者并发环境中,我们常常会遇到这种情况 int i=0; i++ 但是这种写法是线程不安全的。为了达到线程安全的目的,我们通常会用synchronized来修饰对应的代码块。但是synchronized关键字是使用发悲观锁策略,效率不高。就是使用J.U.C包下的atomic类。Atomic原子类是通过自旋CAS操作volatile变量实现的,采用的是乐观锁的策略,性能上高于synchronized。

1.2countdownlatch

CountDownLatch是一个倒计数的锁存器,当计数减至0时触发特定的事件。利用这种特性,可以让主线程等待子线程的结束。其过程是

 设置cnt初始值为n
 CountDownLatch cnt=new CountDownLatch(n);
 线程启动
 调用cnt.await()方法
 线程等待
 T1线程执行完成
 调用cnt.countDown()方法,此时 cnt=n-1
 T2线程执行完成
 调用cnt.countDown()方法,此时 cnt=n-2
 Tn线程执行完成
 调用cnt.countDown()方法,此时 cnt=0
 cnt=0 重新唤醒TA主线程继续执行

1.3cyclicbarrier

和CountDownLatch相似,也是等待某些线程都做完以后再执行。与CountDownLatch区别在于这个计数器可以反复使用。CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;
CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;
CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。

1.4 ReentrantLock

ReentrantLock和synchronized的功能类似,synchronized的特点是使用简单,一切交给JVM去处理,但是功能上是比较薄弱的。在JDK1.5之前,ReentrantLock的性能要好于synchronized,由于对JVM进行了优化,现在的JDK版本中,两者性能是不相上下的。如果是简单的实现,不要刻意去使ReentrantLock。
ReentrantLock可以使用lockInterruptibly()方法来响应中断,tryLock(long timeout, TimeUnit unit)来实现可限时锁。
需要注意的是ReentrantLock必须在finally中使用unlock方法进行解锁操作,如果不进行解锁操作有可能代码出现异常锁没被释放,而synchronized是由JVM来释放锁。

2.线程安全容器

2.1Vector

Vector 主要用在事先不知道数组的大小,或者只是需要一个可以改变大小的数组的情况。Vector的底层数据结构是数组,查询快,增删慢。因为Vector定义的方法使用了synchronized关键字所以保证了线程安全。

2.2Hashtable

和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。
Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。此外,Hashtable中的映射不是有序的。

2.3ConcurrentHashMap

众所周知hashMap是一个不安全的的容器,Hashtable虽然线程安全,但是为了实现线程安全只是简单粗暴的给每个方法加上synchronized关键字,在竞争激烈的时候,效率非常低下。
ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的Hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
JDK1.7实现ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
JDK1.8的实现已经抛弃了Segment分段锁机制,利用CAS+Synchronized来保证并发更新的安全。数据结构采用:数组+链表+红黑树。

2.4ConcurrentLinkedQueue

ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。它采用了“wait-free”算法来实现。
ConcurrentLinkedQueue由head节点和tail节点组成,每个节点(Node)由节点元素(item)和指向下一个节点的引用(next)组成,节点与节点之间就是通过这个next关联起来,从而组成一张链表结构的队列。
默认情况下head节点存储的元素为空,tail节点等于head节点。

  • 使用 CAS 原子指令来处理对数据的并发访问,这是非阻塞算法得以实现的基础。
  • head/tail 并非总是指向队列的头 / 尾节点,也就是说允许队列处于不一致状态。 这个特性把入队 / 出队时,原本需要一起原子化执行的两个步骤分离开来,从而缩小了入队 / 出队时需要原子化更新值的范围到唯一变量。这是非阻塞算法得以实现的关键。
  • 由于队列有时会处于不一致状态。为此,ConcurrentLinkedQueue 使用三个不变式来维护非阻塞算法的正确性。
  • 以批处理方式来更新 head/tail,从整体上减少入队 / 出队操作的开销。
  • 为了有利于垃圾收集,队列使用特有的 head 更新机制;为了确保从已删除节点向后遍历,可到达所有的非删除节点,队列使用了特有的向后推进策略。

3.阻塞队列

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值