我理解的Java并发基础(四):并发锁和原子类

java.util.concurrent是Java为开发者提供的一些高效的工具类,其实现大多基于前文分析的volatile+CAS来实现锁操做。java

java.util.concurrent包含两个子包,java.util.concurrent.atomicjava.util.concurrent.locks编程

java.util.concurrent.atomic是一些原子操做类,好比AtomicBoolean、AtomicInteger、AtomicLong、AtomicIntegerArray等基本数据类型包装类的原子操做 以及 AtomicReference、AtomicReferenceArray等的普通java对象的原子操做类。JDK1.8新增了一些用于并行计算的DobleAccumulator、DoubleAdder、LongAccumulator、LongAdder等类。数组

java.util.concurrent.locks是一些Lock接口及其实现类。可用于开发的实现类包括以前介绍过的 ReentrantLock以及ReentrantReadWriteLock等类,以及Lock相关的工具类LockSupport。安全

除了这两个包以外,java.util.concurrent包下还包括许多的并发类。好比用于Queue的实现类、用于并发流程控制信号类的CountDownLatch、CyclicBarrier、Semaphore类等、用于并发安全集合类的ConcurrentHashMap、CopyOnWriteArrayList类等、用于线程池的Excutors、ThreadPollExecutor类等、用于执行并行任务类的ForkJoinPool、ForkJoinTask等。多线程

本文首先介绍java.util.concurrent.atomic包提供的原子类。并发

  单线程执行代码i = i+1是不会出现意外状况的。可是在多线程语境下,可能获得指望以外的值,好比变 量i=1,A线程更新i+1,B线程也更新i+1,通过两个线程操做以后可能i不等于3,而是等于2。 由于A和B线程在更新变量i的时候拿到的i都是1,这就是线程不安全的更新操做。能够在方法上增长synchronized来解决,由synchronized来保证多线程不会同时更新变量i。可是,单单为了这一个操做就将整个方法或者代码块增长synchronized同步,效率会比较低。因而,java为开发者推出了原子类包java.util.concurrent.atomic,旨在解决这一问题,其底层使用的就是前文提到过的volatile+CAS操做。工具

1,AtomicBoolean、AtomicInteger、AtomicLong,这三个基本类型原子类的方法几乎如出一辙。AtomicInteger的经常使用方法有:atom

void set(int newValue); // 等同于 赋值 i = newValue;
int getAndIncrement(); // 等同于 j = i++;
int getAndDecrement(); // 等同于 j = i--;
int getAndAdd(int delta); // 等同于 j = i; i = i + delta;
int incrementAndGet(); // 等同于 j = ++i;
int decrementAndGet(); // 等同于 j = --i;
int addAndGet(int delta); // 等同于 i = i + delta; j = i;
boolean compareAndSet(int expect, int update); // 直接经过CAS操做来比较
int getAndSet(int newValue); // 等同于 j = i; i = newValue;

2,AtomicReference<V>、AtomicStampedReference<V>,这两个是引用类型原子类。后者是前者的补充,是带有版本号的CAS操做,用于解决只带有值的CAS操做的ABA问题。AtomicReference<V>的经常使用方法有:线程

V get();
void set(V newValue);
boolean compareAndSet(V expect, V update);
V getAndSet(V newValue);

  AtomicStampedReference<V>的经常使用方法有:code

V getReference();
int getStamp();
boolean compareAndSet(V   expectedReference,  V   newReference, int expectedStamp, int newStamp);
void set(V newReference, int newStamp);

3,**AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray<E>**是数组类的原子类。AtomicIntegerArray的经常使用方法有:

int get(int i); // 获取数组索引 i 处的值
void set(int i, int newValue);
int getAndSet(int i, int newValue);
boolean compareAndSet(int i, int expect, int update);
int getAndIncrement(int i);
int getAndDecrement(int i);
int getAndAdd(int i, int delta);
int incrementAndGet(int i);
int decrementAndGet(int i);
int addAndGet(int i, int delta);

4,**AtomicIntegerFieldUpdater<T>、AtomicLongFieldUpdater<T>、AtomicReferenceFieldUpdater<T,V>**这三个是字段级别的原子类。能够对<T>类型的class的指定字段进行原子更新操做。不过这三个类都是抽象类,须要使用静态方法构指明class和field构建后才能使用。AtomicIntegerFieldUpdater<T>的经常使用方法有:

static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName); // 静态方法构指明class和须要原子更新的field
boolean compareAndSet(T obj, int expect, int update);
void set(T obj, int newValue);
int get(T obj);
int getAndSet(T obj, int newValue);
int getAndAdd(T obj, int delta);
int getAndIncrement(T obj);
int getAndDecrement(T obj);
int incrementAndGet(T obj);
int decrementAndGet(T obj);
int addAndGet(T obj, int delta);

  原子操做类是synchronized同步锁的替代者之一,同时java.util.concurrent.locks提供了Lock接口,实现更为丰富的关于同步锁的操做,提供更为高级的synchronized的替代选项。

1,Lock接口最经常使用的实现类是 ReentrantLock 和 ReentrantReadWriteLock。前者表示重入锁,后者表示可重入的读写锁。Lock接口的API以下:

void lock(); // 阻塞获取锁,直到得到锁。
void lockInterruptibly() throws InterruptedException; // 阻塞获取锁,直到得到锁。但在等在获取锁的时候能够响应线程的中断标识。
boolean tryLock(); // 尝试获取锁。若是成功得到锁则返回true。若是没有得到锁则返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 一段时间内尝试获取锁。若是时间段内得到锁则返回true。超时则返回false。
void unlock(); // 释放锁
Condition newCondition(); // 建立一个绑定在该lock对象上的condition对象。

  Condition接口是Lock接口的一个组件,是对Lock功能各位丰富的补充。一个线程只有在获取到lock对象以后,才可使用绑定在该lock接口上的condition对象的方法。Condition接口的经常使用API以下:

void await() throws InterruptedException; // 当前线程进入等待状态,直到被唤醒或中断
void awaitUninterruptibly(); // 当前线程进入等待状态,直到被唤醒,不响应线程中断标识
long awaitNanos(long nanosTimeout) throws InterruptedException; // 一段时间内处于等待状态。超时自动唤醒,容许被唤醒。返回值 = nanosTimeout - 唤醒时的时间
boolean await(long time, TimeUnit unit) throws InterruptedException; // 一段时间内处于等待状态。超时自动唤醒则返回false,被其余线程唤醒则返回true
boolean awaitUntil(Date deadline) throws InterruptedException; // 当前线程进入等待状态,直到指定时间点。若是到达指定时间点自动唤醒,则返回false,被其余线程唤醒则返回true
void signal(); // 唤醒等待该condition对象的其中一个线程
void signalAll(); // 唤醒等待该condition对象的全部线程

  获取一个Condition只能经过Lock的newCondition()方法。同一lock对象上能够绑定多个condition对象,但同一时间最多只有一个condition对象能够执行程序。

2,ReentrantLock表示可重入锁。持有该对象锁的线程能够对资源进行重复加锁。在该类的构造方法中能够指定公平锁和非公平锁。
2.1,可重入性
  线程执行方法A,使用lock.lock()方法成功获取到锁,而后调用方法B,当方法B内执行到lock.lock()的时候(这两个lock是经过一个lock对象),若是能顺利执行,则说明该lock对象具备可重入性。不然不具备可重入性。可类比synchronized关键字进行理解,由于synchronized关键字属于JVM层面的隐性的具备可重入性。tips:千万不要忘记在finally中使用lock.unlock()进行锁资源的释放。
2.2,公平锁和非公平锁
  ReentrantLock内部是经过AbstractQueuedSynchronizer同步器(AQS)来实现的。AQS同步器内部维护了一个FIFO(先入先出)的阻塞队列 和 一个int类型的状态码。所谓的公平性,就是当队列不为空的时候,若是有新的线程争夺锁资源,则该新的线程必须加入到队列的尾部。当有锁释放的时候,只能由队列的头部对应的线程来进行锁资源的获取。所谓的非公平性,就是当有锁释放的时候,队列的头部所对应的线程 和 新的线程 均可以争夺锁资源。若是新的线程成功得到锁对象则执行后续操做,若是获取锁对象失败则加入到队列尾部。
  因为AQS同步器获取锁资源的操做是经过CAS操做来完成的,刚释放锁的线程再次获取锁的概率会很是大,使得其余线程只能在同步队列中等待。因此非公平锁的效率要优于公平锁,吞吐量更大。

3,ReentrantReadWriteLock表示可重入的读写锁。具备可重入性,构造方法中能够指定公平锁和非公平锁。ReentrantReadWriteLock内部维护了一对锁,一个写锁和一个读锁。容许多个线程对资源进行读的访问,只有一个线程对资源进行写操做。
3.1,可重入性。 同ReentrantLock。
3.2,公平锁和非公平锁。同ReentrantLock。
3.3,锁降级
  当一个线程在持有了写锁并对资源进行写操做以后,再去获取读锁,而后再释放写锁。这个过程称为是锁降级。锁降级中读锁的获取是否必要呢?答案是必要的,主要是为了保证数据的可见性。当线程A获取写锁并进行写操做以后释放锁,可是获取读锁的时候失败,由于线程B获取到了写锁。这种状况下,线程B对资源进行写以后,线程A是并不知道的,线程A会使用以前的“脏数据”,会形成并发安全问题。

4,LockSupport是操做线程状态的并发工具类。经常使用的API以下:

static void unpark(Thread thread); // 唤醒处于阻塞状态的线程thread
static void park(); // 阻塞当前线程,直到被唤醒或中断
static void parkNanos(long nanos); // 阻塞当前线程,最长不超过nanos纳秒
static void parkUntil(long deadline); // 阻塞当前线程,最长不超过deadline时间点

// 如下为JDK1.6新增API,参数blocker是用来标识当前线程在等待的对象,主要用于问题排查和系统监控。
static void park(Object blocker); // 同park()
static void parkNanos(Object blocker, long nanos); // 同parkNanos(long nanos)
static void parkUntil(Object blocker, long deadline); // 同parkUntil(long deadline)

  LockSupport线程阻塞采用的是Unsafe类的native操做,与JVM层面的object.wait()使线程阻塞不一样。即,使用LockSupport.park(Object blocker)后阻塞的线程,不能经过blocker.notify()唤醒,反之亦然。

参考资料:

  • 《Java并发编程的艺术》
  • 《深刻理解Java虚拟机:JVM高级特性与最佳实践》
  • 以上内容为笔者平常琐屑积累,已无从考究引用。若是有,请站内信提示。
相关文章
相关标签/搜索