Java的数据会在CPU、Register、Cache、Heap和Thread stack之间进行复制操做,而前面四个都是在Java Threads之间共享,所以Java的锁机制主要用于解决Racing Threads的数据一致性。编程
基于CPU缓存一致性MESI协议的volatile关键字缓存
保证变量在racing thread之间实时可见,使用内存屏障禁止JVM基于instruction reorder的优化,不能保证变量在各个线程之间的数据一致性(如counter++);安全
基于字节码机制的synchronized关键字
synchronized是语言自带的内置独享锁、非公平锁,也就是无论racing thread排队的时间前后都统一进行锁竞争,经过编排JVM字节码实现,锁为对象或者类的头标记位;悲观锁,并发性差;另外synchronized声明不会被继承,也就是若是子类方法重写父类的synchronized方法时,再也不具备同步性。多线程
使用synchronized关键字修饰代码块或者方法,表示这块代码为互斥区或临界区。有两种类型的锁能够经过synchronized加到代码块或者方法上,一种是实例Object锁,一种是class锁。对于同一个ClassLoader下加载的类而言,一个类只有一把class锁,全部这个类的实例都共享一把类锁;同一个类能够实现多个实例对象,也就存在多个实例锁。另外经过synchronized添加的锁具备可重入性,也就是只要一个线程已经获取了锁,这样只要共享同一把锁的其余synchronized修饰的代码块或者方法均可以进入,换句话说其余线程访问对其余synchronized修饰的代码块或者方法也须要等待锁的释放,所以synchronized还支持任意对象的锁,这样同一个类的不一样方法能够添加不一样的对象锁。并发
下面是基于不一样锁实现的synchronized块:对象锁,静态对象锁,类锁框架
1 public class App1 { 2 private Object lock1 = new Object(); 3 private static Object lock2 = new Object(); 4 synchronized public void funcA() { 5 //this object lock 6 } 7 public void funcB() { 8 synchronized(this) { 9 //this object lock 10 } 11 //run something without lock 12 } 13 public void funcC(List<String> list) { 14 synchronized(list) { 15 //list object lock 16 } 17 } 18 public void funcD() { 19 synchronized(lock1) { 20 //lock1 object lock 21 } 22 } 23 public void funcE() { 24 synchronized(lock2) { 25 //lock2 static object lock 26 } 27 } 28 public void funcF() { 29 synchronized(App1.class) { 30 //App1 class lock 31 } 32 } 33 synchronized public static void funcG() { 34 //App1 class lock 35 } 36 }
基于Abstract Queued Synchronizer(AQS)机制的ReentraintLock
AQS是JDK提供的的一种实现,能够实现公平和非公平锁,内部实现依赖volatile int state变量加CLH队列实现,主要的实现类是ReentrantLock;AQS定义了多线程访问共享资源的同步器框架,常见的如ReentraintLock/Semaphore/CountDownLatch等都依赖于AQS的实现;ide
1 private volatile int state; 2 static final class Node { 3 int waitStatus; 4 Node prev; 5 Node next; 6 Node nextWaiter; 7 Thread thread; 8 } 9 protected final int getState() { return state; } 10 protected final void setState(int newState) { state = newState; } 11 12 protected final boolean compareAndSetState(int expect, int update) { 13 // See below for intrinsics setup to support this 14 return unsafe.compareAndSwapInt(this, stateOffset, expect, update); 15 } 16 protected boolean tryAcquire(int arg) {} 17 protected boolean tryRelease(int arg) {} 18 protected int tryAcquireShared(int arg) {} 19 protected boolean tryReleaseShared(int arg) {} 20 protected boolean isHeldExclusively() {}
AQS经过维护一个FIFO队列,而且经过一个由volatile修饰的int状态值来实现锁的获取,int状态值经过CPU的compare-and-swap指令进行更新,从而保证原子性。FIFO队列中每个Node表示一个排队线程,其保存着线程的引用和状态,而后经过三个方法分别对获取或者设置状态。经过对getState,setState和compareAndSetState的封装,AQS的继承类须要试下以下几个方法,前面两个表示获取和释放独占锁(如ReentraintLock),后面两个表示获取和释放共享锁(如Semaphore和CountDownLatch)。优化
ReentrantLock初始化状态state=0,线程A访问同步代码的时候使用ReentrantLock.lock(),内部会调用tryAcquire尝试获取独占锁,状态变成state+1;其余线程调用ReentrantLock.lock()的时候就会失败,直到线程A调用unlock(内部为tryRelease)将状态编程state=0;若是线程A在持有独占锁的同时访问其余同步代码块,这时候state的值就会累加,须要调用unlock(内部为tryRelease)减小state的值。ReentrantLock也提供了相似wait/notify的方法,await/signal,一样的线程在调用这两个方法以前须要得到对象锁监视,也就是执行lock.lock()方法。ui
ReentrantLock是纯粹的独占锁,为了提高效率引入了ReentrantReadWriteLock –> readLock/writeLock,读读共享,读写互斥,写写互斥。CLH队列中的节点模式分为shared和exclusive两种,当一个线程修改了state状态则表示成功获取了锁,若是线程的模式是shared则会执行一个传递读锁的过程,策略是从CLH队列的头到尾依次传递读锁,直到遇到一个模式为exclusive的写锁模式的节点,这个exclusive模式的节点须要等以前全部shared模式的节点对应的操做都执行完毕以后才会获取到锁,这就是读写锁的模式。this
1 public class App1 extends Thread { 2 private Lock lock = new ReentrantLock(); 3 private Condition condition = lock.newCondition(); 4 public App1() { 5 super(); 6 } 7 @Override 8 public void run() { 9 try { 10 lock.lock(); 11 System.out.println(Thread.currentThread().getName() 12 + " : start to wait."); 13 condition.await();//condition.signal(); 14 System.out.println(Thread.currentThread().getName() 15 + " : wait ends, execute again."); 16 } catch (Exception e) { 17 } finally { 18 lock.unlock(); 19 } 20 } 21 }
基于Compare-And-Swap机制的AtomicBoolean/AtomicReference/AtomicInteger,可实现自旋锁(spin-lock),乐观锁
基于volatile关键字的变量虽然能够保证各个线程的实时可见,但一旦发生更新操做,则可能发生线程间数据不一致的发生,常见的一种场景是多线程共享的计数器,volatile不能友好解决counter++这样的更新操做,这个时候可使用AtomicInteger来保证;AtomicInteger内部结合volatile修饰int变量,native类型的unsafe.compareAndSwapInt操做,和基于内存偏移量的compare-and-swap操做实现乐观锁的原子操做,最终能够保证Atomic类数据更新的线程安全;
下面代码是AtomicInteger类全部封装操做的内部实现,目标变量的内存值能够经过aInteger和objectValueOffset惟一肯定,首先取出指望值current,而后经过自旋操做对比当前线程的指望值current与当前内存中的值是否匹配,若是匹配则更新为current + increment,不然继续自旋;排队自旋锁(ticket spin-lock)正是基于这一机制实现的;
1 public final int getAndAddInt(Object aInteger, long objectValueOffset, 2 int increment) { 3 int current; 4 do { 5 current = this.getIntVolatile(aInteger, objectValueOffset); 6 } while (!this.compareAndSwapInt(aInteger, objectValueOffset, 7 current, current + increment)); 8 return current; 9 }
相比synchronized关键字的悲观锁实现,CAS使用乐观锁机制在并发性方面具备比较大的优点,但受限于CAS的实现机制,AtomicInteger也存在一些问题,ABA问题,CPU开销大,不能保证两个或者以上变量的原子性操做;