zoukankan      html  css  js  c++  java
  • 对Java提供的锁机制的一些思考

    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的实现;

     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()方法。

    ReentrantLock是纯粹的独占锁,为了提升效率引入了ReentrantReadWriteLock –> readLock/writeLock,读读共享,读写互斥,写写互斥。CLH队列中的节点模式分为shared和exclusive两种,当一个线程修改了state状态则表示成功获取了锁,如果线程的模式是shared则会执行一个传递读锁的过程,策略是从CLH队列的头到尾依次传递读锁,直到遇到一个模式为exclusive的写锁模式的节点,这个exclusive模式的节点需要等之前所有shared模式的节点对应的操作都执行完毕之后才会获取到锁,这就是读写锁的模式。

     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开销大,不能保证两个或者以上变量的原子性操作;

  • 相关阅读:
    angularjs 判断是否包含 permIDs|filter:'10'
    js日期格式化
    JSON格式检验
    CodeSmith Generator 6.5
    Hosts文件说明
    正则表达式匹配换行实例代码
    Codeforces 311E Biologist
    URAL 1349 Farm
    [SDOI2015] 序列统计
    洛谷 P3803 多项式乘法
  • 原文地址:https://www.cnblogs.com/leo-chen-2014/p/10229305.html
Copyright © 2011-2022 走看看