Atomic数据类型
Atomic数据类型有四种类型:AtomicBoolean, AtomicInteger, AtomicLong, 和AtomicReferrence(针对Object的)以及它们的数组类型,还有一个特殊的AtomicStampedReferrence,它不是AtomicReferrence的子类,而是利用AtomicReferrence实现的一个储存引用和Integer组的扩展类
所有原子操作都是依赖于sun.misc.Unsafe这个类,这个类底层是由C++实现的,利用指针来实现数据操作
value成员都是volatile的
锁
Synchronize和显示锁比较
- 可中断获取锁:使用synchronized关键字获取锁的时候,如果线程没有获取到被阻塞了,那么这个时候该线程是不响应中断(interrupt)的,而使用Lock.lockInterruptibly()获取锁时被中断,线程将抛出中断异常。
- 可非阻塞获取锁:使用sync关键字获取锁时,如果没有成功获取,只有被阻塞,而使用Lock.tryLock()获取锁时,如果没有获取成功也不会阻塞而是直接返回false。
- 可限定获取锁的超时时间:使用Lock.tryLock(long time, TimeUnit unit)。
- 其实显示锁还有其他的优势,比如同一锁对象上可以有多个等待队列(相当于Object.wait())。使用比较复杂,这点之前提到了,需要手动加锁,解锁,而且还必须保证在异常状态下也要能够解锁。而synchronized的使用就简单多了。
- 效率较低,synchronized关键字毕竟是jvm底层实现的,因此用了很多优化措施来优化速度(偏向锁、轻量锁等),而显示锁的效率相对低一些。
AQS(AbstractQueuedSynchronizer)实现原理
自Jdk1.5开始包含了并发大神Doug Lea写的并发工具包java.util.concurrent,这个工具包中包含了显示锁和其他的实用同步组件。Doug Lea在构建锁和组件的时候,大多是以队列同步器(AbstractQueuedSynchronizer)为基础的,因此AbstractQueuedSynchronizer可以看作是并发包的基础框架。因此掌握了AbstractQueuedSynchronizer的实现原理,也就掌握了大多数并发组件的实现原理。
AQS使用一个int变量state表示同步状态,使用一个隐式的FIFO同步队列(隐式队列就是并没有声明这样一个队列,只是通过每个节点记录它的上个节点和下个节点来从逻辑上产生一个队列)来完成阻塞线程的排队。
这里FIFO队列的节点在AQS中被定义为一个内部类Node,Node的主要字段有:
- waitStatus:等待状态,所有的状态见下面的表格。
- prev:前驱节点
- next:后继节点
- thread:当前节点代表的线程
- nextWaiter:Node既可以作为同步队列节点使用,也可以作为Condition的等待队列节点使用(将会在后面讲Condition时讲到)。在作为同步队列节点时,nextWaiter可能有两个值:EXCLUSIVE、SHARED标识当前节点是独占模式还是共享模式;在作为等待队列节点使用时,nextWaiter保存后继节点。
状态 | 值 | 含义 |
CANCELLED | 1 | 当前节点因为超时或中断被取消同步状态获取,该节点进入该状态不会再变化 |
SIGNAL | -1 | 标识后继的节点处于阻塞状态,当前节点在释放同步状态或被取消时,需要通知后继节点继续运行。每个节点在阻塞前,需要标记其前驱节点的状态为SIGNAL。 |
CONDITION | -2 | 标识当前节点是作为等待队列节点使用的。 |
PROPAGATE | -3 |
|
0 | 0 | 初始状态 |
ReentrantLock实现原理
1、使用 synchronize 来做同步处理时,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现。而 ReentrantLock 就是一个普通的类,是基于 AQS(AbstractQueuedSynchronizer,队列是由node节点组成的双向链表实现的)来实现的;是一个重入锁:一个线程获得了锁之后仍然可以反复的加锁,不会出现自己阻塞自己的情况。
2、ReentrantLock分为公平锁和非公平锁,默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
由于公平锁需要关心队列的情况,得按照队列里的先后顺序来获取锁(会造成大量的线程上下文切换),而非公平锁则没有这个限制,效率和吞吐量比公平锁高。
未完待续......