并发编程-2-Java工具原理

2.常用Java并发工具的原理

常用java并发工具

volatile
synchronized
AQS
原子操作类
线程池

 

2.1volatile变量的作用及实现原理

2.1.1可见性问题

当cpu1改变A的值后,cpu2读取A的值到B,但此时可能cache1的缓存还未同步到主内存,导致可见性问题。

 

2.1.2volatile解决可见性问题

volatile

volatile private boolean opened;

增加volatile的变量引起CPU层面的变化:增加lock前缀指令

汇编

0x0000000002931351: lock add dword ptr [rsp],0h

基本描述:volatile,保证共享变量多线程可见性,轻量级的synchronized,不引起线程上下文切换

基本保证:

1)当前CPU缓存行的数据写回到系统内存

2)写回操作使其他CPU里缓存了该内存地址的数据无效

CPU层面变化:

lock在CPU层面使用锁总线或锁缓存(缓存一致性协议)来实现,锁总线开销大,近期的CPU一般使用锁缓存的机制

锁总线:

锁总线,其它CPU对内存的读写请求都会被阻塞,直到锁释放

锁缓存:

缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

参考:[https://www.cnblogs.com/xrq730/p/7048693.html]

 

2.2synchronized作用和实现原理

synchronized

//静态方法同步
private static synchronized void staticSyncFunc(){
//do something
}

//普通方法同步
private synchronized void syncFunc(Object obj){
//同步对象
  synchronized(obj){
  //do something
  }
}

java锁的是对象,对象内存结构:

对象结构长度:

锁的表现形式

锁的具体对象

普通方法

当前实例对象

静态方法

当前class对象

同步方法块

Synchronized括号里配置的对象

锁升级流程

锁升级流程:

 

重量级锁:

 

Java6后的优化手段

偏向锁:

偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。

记录线程id,若id一致,则直接获取锁,否则膨胀。

轻量级锁:

CAS更新指向栈中锁记录的指针,若成功,则获取锁,否则膨胀。

偏向锁与轻量级锁均为乐观锁。

重量级锁为悲观锁。

 

参考[Java并发编程的艺术.第2章]

参考[http://www.cnblogs.com/dennyzhangdd/p/6734638.html]对java并发编程的艺术上的内容复述及总结

参考[https://blog.csdn.net/boling_cavalry/article/details/77995069]

参考[http://www.hollischuang.com/archives/2030]monitor原理及源码分析

参考[https://www.jianshu.com/p/c5058b6fe8e5]hotspot c++源码分析

参考[https://www.jianshu.com/p/e53e7964db03]hotspot编译方法

 

2.3原子操作作用和原理

原子操作:不可被中断的一个或一系列操作

Atomic

public class AtomicIntegerTest {

    static AtomicInteger ai = new AtomicInteger(1);

    public static void main(String[] args) {
        System.out.println(ai.getAndIncrement());
        System.out.println(ai.get());
    }

}

public class AtomicInteger extends Number implements java.io.Serializable {
  public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

Unsafe

// setup to use Unsafe.compareAndSwapInt for updates 
private static final Unsafe unsafe = Unsafe.getUnsafe();

查看源码发现Atomic包里的类基本都是使用Unsafe实现的,Unsafe只提供了以下三种CAS方法。

CAS

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);  
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);  
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

2.4经典等待通知机制

 

等待通知示例

public class WaitNotify {
    static boolean flag = true;
    static Object lock = new Object();

    public static void main(String[] args) throws Exception {
        Thread waitThread = new Thread(new Wait(), "WaitThread");
        waitThread.start();
        TimeUnit.SECONDS.sleep(1);

        Thread notifyThread = new Thread(new Notify(), "NotifyThread");
        notifyThread.start();
    }

    static class Wait implements Runnable {
        public void run() {
            // 加锁,拥有lock的Monitor
            synchronized (lock) {
                // 当条件不满足时,继续wait,同时释放了lock的锁
                while (flag) {
                    try {
                        System.out.println(Thread.currentThread() + " flag is true. wait @ "
                                           + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                        lock.wait();
                    } catch (InterruptedException e) {
                    }
                }
                // 条件满足时,完成工作
                System.out.println(Thread.currentThread() + " flag is false. running @ "
                                   + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }
        }
    }

    static class Notify implements Runnable {
        public void run() {
            // 加锁,拥有lock的Monitor
            synchronized (lock) {
                // 获取lock的锁,然后进行通知,通知时不会释放lock的锁,
                // 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
                System.out.println(Thread.currentThread() + " hold lock. notify @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                lock.notifyAll();
                flag = false;
                SleepUtils.second(5);
            }
            // 再次加锁
            synchronized (lock) {
                System.out.println(Thread.currentThread() + " hold lock again. sleep @ "
                                   + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                SleepUtils.second(5);
            }
        }
    }
}

等待通知流程:

2.5AQS队列同步器作用及源码分析

 

synchronized实现基本锁功能,AQS同步器提供更多的多线程工具,提供更多的使用场景

AQS描述

 

Java

AQS为依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)实现提供框架。
​
它使用了一个原子的int value status来作为同步器的状态值(如:独占锁。1代表已占有,0代表未占有),
通过该类提供的原子修改status方法(getState, setState and compareAndSetState),我们可以把它作为同步器的基础框架类来实现各种同步器。 
​
AQS还定义了一个实现了Condition接口的ConditionObject内部类。
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,
以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。 
简单来说,就是Condition提供类似于Object的wait、notify的功能signal和await,使一个正在执行的线程挂起(推迟执行),直到被其他线程唤醒。
但是Condition更加强大,如支持多个条件谓词、保证线程唤醒的顺序和在挂起时不需要拥有锁。
使用AQS同步器的锁
CountDownLatch
Mutex
OneShotLatch
ReentrantLock
ReentrantReadWriteLock
Semaphore
TwinsLock

AQS主要成员变量:

private transient volatile Node head;//等待队列头部
private transient volatile Node tail;//等待队列尾部
private volatile int state;//AQS状态位,通过try*方法维护

AQS内部类Node:

static final class Node {
    static final Node SHARED = new Node();//标识等待节点处于共享模式
    static final Node EXCLUSIVE = null;//标识等待节点处于独占模式
​
    static final int CANCELLED =  1;//由于超时或中断,节点已被取消
    static final int SIGNAL    = -1;//表示下一个节点是通过park堵塞的,需要通过unpark唤醒
    static final int CONDITION = -2;//表示线程在等待条件变量(先获取锁,加入到条件等待队列,然后释放锁,等待条件变量满足条件;只有重新获取锁之后才能返回)
    static final int PROPAGATE = -3;//表示后续结点会传播唤醒的操作,共享模式下起作用
​
    //等待状态:对于condition节点,初始化为CONDITION;其它情况,默认为0,通过CAS操作原子更新
    volatile int waitStatus;
    //前节点
    volatile Node prev;
    //后节点
    volatile Node next;
    //线程对象
    volatile Thread thread;
    //对于Condtion表示下一个等待条件变量的节点;其它情况下用于区分共享模式和独占模式;
    Node nextWaiter;
​
    final boolean isShared() {
        return nextWaiter == SHARED;//判断是否共享模式
    }
    //获取前节点,如果为null,抛出异常
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
​
    Node() {    // Used to establish initial head or SHARED marker
    }
​
    Node(Thread thread, Node mode) {     //addWaiter方法使用
        this.nextWaiter = mode;
        this.thread = thread;
    }
​
    Node(Thread thread, int waitStatus) { //Condition使用
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

AQS主要函数:

Mutex lock 调用AQS Acquire:

public void lock() {
  sync.acquire(1);
}

Acquire完成:

1.锁尝试获取(tryAcquire),

2’.获取到锁,则结束

2.若获取不到,则通过addWaiter方法构造节点并放入队列,

3.再通过acquireQueued的方法自旋阻塞获取锁

Mutex tryAcquire

// 当状态为0的时候获取锁
public boolean tryAcquire(int acquires) {
  assert acquires == 1; // Otherwise unused
  if (compareAndSetState(0, 1)) {
    setExclusiveOwnerThread(Thread.currentThread());
    return true;
  }
  return false;
}
Acquire
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
AddWaiter
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
enq
private Node enq(final Node node) {
  for (;;) {
    Node t = tail;
    if (t == null) { // Must initialize
      if (compareAndSetHead(new Node()))
        tail = head;
    } else {
      node.prev = t;
      if (compareAndSetTail(t, node)) {
        t.next = node;
        return t;
      }
    }
  }
}
AddWaiter流程解析:

acquireQueued

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;//未发生中断
        //仍然通过自旋,根据前面的逻辑,此处传入的为新入列的节点
        for (;;) {
            final Node p = node.predecessor();//获取前节点,即prev指向节点
           //如果node的前一节点为head节点,而head节点为空节点,说明node是等待队列里排在最前面的节点
            if (p == head && tryAcquire(arg)) {
              //获取资源成功,将node设置为头节点,setHead清空节点属性thread,prev
                setHead(node);
                p.next = null; // 将原头节点的next设为null,帮助GC
                failed = false;
                return interrupted;//返回是否发生中断
            }
            //如果acquire失败,是否要park,如果是则调用LockSupport.park
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;//发生中断
        }
    } finally {
        if (failed)//只有循环中出现异常,才会进入该逻辑
            cancelAcquire(node);
    }
}
shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //如果acquireQueued第一次调用该方法,ws==0
    int ws = pred.waitStatus;
   //已经设置了状态,由于SIGNAL表示要通过unpark唤醒后一节点,因此当获取失败时,是要调用park堵塞的,返回true
    if (ws == Node.SIGNAL)
        return true;
    //如果前一节点已取消,则往前找,直到找到一个状态正常的节点,其实就是从队列删除取消状态的节点
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;//更新next指针,去掉中间取消状态的节点
    } else {//更新pred节点的waitStatus为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;//返回false,表示不需要调用park
}
parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
  //将当前线程的parkBlocker变量指向this,调用unsafe.park堵塞当前线程
  //简单来说park是申请许可,如果存在许可,马上返回,否则一直等待获得许可;unpark是将许可数量设为1,会唤醒park返回;
  //LockSupport提供了unpark(Thread thread)方法,可以为指定线程颁发许可
  LockSupport.park(this);
  return Thread.interrupted();//注意:该方法会清除线程的中断状态
}
release
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//unpark唤醒第一个等待节点
        return true;
    }
    return false;
}
参考[https://www.jianshu.com/p/c244abd588a8]AQS源码分析
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值