并发编程时首先考虑的是线程安全问题,线程安全,指的是多线程访问下,某个函数被多个线程调用多次,都能够正确处理各个线程的局部变量,并且计算结果正确。

解决线程安全问题,一般有三种方式:

使用 ThreadLocal 避免线程共享变量

使用 synchronized 和 lock 进行同步控制

使用原子类声明变量

锁用来做同步控制,可以控制多个线程访问共享资源的顺序,Java 中内置的锁 synchronized 一直饱受争议,虽然在 JDK1.6 之后有所优化,但是基于隐式的获取锁和释放锁操作,一定程度上减少了获取锁和释放锁的可操作性和灵活性。

Java 中的 Lock 锁是基于队列同步器 AQS (AbstractQueuedSynchronized) 实现的,AQS 是构建 ReentrantLock、Semaphore、ReentrantReadWriteLock、CountDownLatch 和 FutureTask 的基础框架。

队列同步器是用来做同步控制的,需要最基本的需要两点功能:

同步状态的获取

同步状态的释放

AQS 提供了那些方法对同步状态进行管理呢?

getState():返回同步状态的当前值;

setState(int newState):设置当前同步状态;

compareAndSetState(int expect, int update):使用 CAS 设置当前状态,该方法能够保证状态设置的原子性;

tryAcquire(int arg):独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态;

tryRelease(int arg):独占式释放同步状态;

tryAcquireShared(int arg):共享式获取同步状态,返回值大于等于 0 则表示获取成功,否则获取失败;

tryReleaseShared(int arg):共享式释放同步状态;

isHeldExclusively():当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占;

acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的 tryAcquire(int arg) 方法;

acquireInterruptibly(int arg):与 acquire(int arg) 相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出 InterruptedException 异常并返回;

tryAcquireNanos(int arg,long nanos):超时获取同步状态,如果当前线程在 nanos 时间内没有获取到同步状态,那么将会返回 false,已经获取则返回 true;

acquireShared(int arg):共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;

acquireSharedInterruptibly(int arg):共享式获取同步状态,响应中断;

tryAcquireSharedNanos(int arg, long nanosTimeout):共享式获取同步状态,增加超时限制;

release(int arg):独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒;

releaseShared(int arg):共享式释放同步状态。

AQS 是怎么实现的呢?

队列同步器依赖一个 FIFO 双向队列来完成同步状态的管理。

CLH 队列同步器结构如下:

java lock 锁对象 java lock aqs_java如何理解锁

当前线程获取同步状态失败时,将当前线程和等待状态信息构成一个节点 (Node)并将其加入同步队列,同时阻塞当前线程。

当同步状态释放时,会唤醒下一个节点,并设置成首节点,使其再去获取同步状态。

Node 节点有哪些属性?

static final class Node {

/** Marker to indicate a node is waiting in shared mode */

static final Node SHARED = new Node();

/** Marker to indicate a node is waiting in exclusive mode */

static final Node EXCLUSIVE = null;

/** 值为1,由于同步队列中等待的线程超时或者被中断,需要到同步队列中取消等待,节点进入该状态将不会变*/

static final int CANCELLED = 1;

/**后继节点的线程处于阻塞状态,而如果当前节点的线程如果释放同步状态或者被取消,通知后继节点,使得后继节点可以运行*/

static final int SIGNAL = -1;

/** 值为-2 节点在等待队列中,节点线程等待在Condition上,当其他线程对 Condition 调用了 signal() 方法后,该节点会从等待队列转移到同步队列中,进行同步状态的获取 */

static final int CONDITION = -2;

/**

表示下次共享状态的获取将会无跳转的传播下去

*/
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
/** 等待队列中的后继节点*/
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}

CLH 队列的入队出队是怎么实现的?

java lock 锁对象 java lock aqs_线程安全_02