并发编程-同步锁学习笔记

synchronized(隐式锁)

  • 同步实例方法:锁加载在类上,new出来的不同的对象也可以保持同步。
  • 同步类方法:锁加载非static方法,锁的是this对象,new出来的不同的对象不会同步。
  • 同步代码块:只锁当前代码块。

底层原理

synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁)实现,它是一个重量级锁性能较低。当然,JVM内置锁在1.5之后版本做了重大的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销,,内置锁的并发性能已经基本与Lock持平。synchronized关键字被编译成字节码后会被翻译成monitorenter 和 monitorexit 两条指令分别在同步块逻辑代码的起始位置与结束位置。
锁加在类上的时候,翻译成字节码后会用ACC_SYNCHRONIZED修饰。

锁升级

对象头

在这里插入图片描述

偏向锁

jvm在启动的时候会有偏向锁延迟,大概五秒左右,所以jvm刚启动时,对象头会是轻量级锁的状态。
当线程A第一次执行代码同步块时,偏向锁的指针会指向该线程。当下一次执行该代码块时,判断偏向锁是否还存在,如果A线程已经释放,则进行重偏向,若A没有释放,升级为轻量级锁。

轻量级锁

当又一个线程执行代码块时,通过CAS尝试修改轻量级锁记录指针,如果修改成功则获取轻量级锁,修改失败则膨胀为重量级锁

自旋锁

轻量级锁失败后,虚拟机通常会进行50或者100次自旋,如果自旋成功,也不会进行锁的升级。

重量级锁

重量级锁通过monitor保证同步。

锁粗化

锁消除

消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间,如下StringBuffer的append是一个同步方法,但是在add方法中的StringBuffer属于一个局部变量,并且不会被其他线程所使用,因此StringBuffer不可能存在共享资源竞争的情景,JVM会自动将其锁消除。锁消除的依据是逃逸分析的
:-XX:+DoEscapeAnalysis 开启逃逸分析
-XX:+EliminateLocks 表示开启锁消除。

逃逸分析

开启逃逸分析, -XX:+DoEscapeAnalysis : 表示开启逃逸分析 -XX:-DoEscapeAnalysis
jdk1.7以后默认开启逃逸分析。线程逃逸分析后,new出来的对象可能会放在栈里,而不是放在堆里。

Lock(显示锁)

LockSupport

LockSupport是rt.jar包里的类,用来挂起和唤醒线程。
主要方法有:

  • void park() 阻塞线程,不抛出InterruptedException异常
  • void unpark(Thread thread) 唤醒线程,可由任意其它线程唤醒指定线程
  • void parkNonos(long nanos) 阻塞线程,超时则返回
  • void park(Object blocker) 挂起线程时记录blocker,可以协助通过jstack pid定位问题。
  • void parkNanos(Object blocker, long nanos) 比上面方法多了个超时时间
  • void parkUntil(Object blocker, long deadline) deadline是距离元年时间的总毫秒。
    park方法可以被Thread.interrupted方法取消中段标志。

interrupted

  • public void interrupt() 中断此线程
  • public static void interrupted() 查询线程是否中断,并且清除中断标识,包括被park阻塞的
  • public boolean isInterrupted() 查询线程是否中断,不会清除中断标识
    被中断的标识不会让线程停止,只有在调用sleep,wait,notify这些对线程存在操作时,会去检查线程是否被中断,interrupted只能给线程打上中断标记,需要由线程自己去处理中段,已经处于RUNABLE的线程就无法中断了,所以如果线程发生死循环,死锁,是无法被中断,只能重启进程。

基于AQS框架实现,支持手动加锁和解锁,支持公平锁,允许中断,支持可重入,支持独占/共享锁。
ReentrantLock核心:自旋、LockSupport、CAS、队列

在这里插入图片描述
ReentrantLock默认是非公平锁。公平锁就是T0占用锁,T1、T2正在排队,当T0释放时,又来一个T3,T3可以直接抢占锁,不需要排队。如果T3也要去排队,就是公平锁。
依赖树

AQS

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {

    /** Use serial ID even though all fields transient. */
    private static final long serialVersionUID = 3737899427754241961L;

    /**
     * Empty constructor for use by subclasses.
     */
    protected AbstractOwnableSynchronizer() { }

    /**
     * 独占模式同步的当前所有者线程信息。 表示当前获取线程的是谁。
     */
    private transient Thread exclusiveOwnerThread;

    /**
     * 设置当前拥有独占访问权的线程。空参数表示没有线程拥有访问权。除此之外,此方法不强制任何同步或volatile字段访问。
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    /**
     * 返回setExclusiveOwnerThread最后设置的线程,如果没有设置则返回null。除此之外,此方法不强制任何同步或volatile字段访问.
     */
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

AbstractQueuedSynchronizer类中有一个内部类Node(双端队列)。

/* 节点的生命状态,信号量,
 *SIGNAL(-1):后继节点的线程处于等待状态,而当前的节点如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行。
 CANCELLED(1):在同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消等待
 CONDITION(-2):节点在等待队列中,节点的线程等待在Condition上,当其他线程对Condition调用了signal()方法后, 
 该节点会从等待队列中转移到同步队列中,加入到同步状态的获取中
 PROPAGATE(-3):表示下一次共享式同步状态获取将会被无条件地传播下去
 0:以上均为零。数值以数字形式排列,以简化使用。非负值意味着节点不需要发送信号。因此,大多数代码不需要检查特定的值,只需要检查符号。
 对于正常同步节点,该字段初始化为0,对于条件节点,该字段初始化为CONDITION。
 它使用CAS(或者在可能的情况下,使用无条件的volatile写入)进行修改。
 */
volatile int waitStatus;
// 前驱节点,当前节点加入到同步队列中被设置
volatile Node prev;
// 前驱节点,当前节点加入到同步队列中被设置
volatile Node next;
// state在不同的应用中代表不同的意思,比如在CountDownLatch中代表线程个数,在读写锁中用高低位表示读锁和写锁的持有个数
private volatile int state; 

公平锁

重要方法

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState(); // 获取状态
            if (c == 0) { // 没有被锁
                if (!hasQueuedPredecessors() && // 队列中是否已经有排队了
                    compareAndSetState(0, acquires)) { // 把状态改为1
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { 
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
// 线程入队(同步等待队列CLH)
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)) { // 入队也是CAS
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
    
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {// 尝试再去获取锁
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt()) // 加入到队列中后,判断是否调用LockSupport.park(this)阻塞线程,并且通过Thread.interrupted()清除线程阻塞信号
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    // 实现公平的逻辑,把首节点waiteState改为-1(可唤醒状态)
     private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

    static void selfInterrupt() {
        Thread.currentThread().interrupt();// 把线程阻塞信号发送到外部。interrupt会唤醒park的阻塞。
    }

lock.lockInterruptibly()和lock.lock不同,前者被阻塞后,抛出InterruptedException(),这是ReentrantLock实现可中断的原理。

条件队列和同步队列

线程获取锁的条件是:只有在CLH队列里等待的Node节点并且是Node节点的前驱节点是Sinal,条件队列里的线程是不可以获取锁的。
条件队列是实现BLockQueue的基础,这里记录一下newCondition.await和object.wait、newCondition.signal和object.notify的不同是在于,newConditon可以实现精准等待和唤醒。

 /** Condition for waiting takes */
    private final Condition notEmpty;// 队列是否为空

    /** Condition for waiting puts */
    private final Condition notFull; // 队列是否满了

阻塞队列种,使用了notfull和notEmpty来控制是否阻塞消费者或者生成者,如果已经满了,会把生产者放入条件队列中,然后消费者消费后,队列可以继续插入数据的时候,再从条件队列放到同步队列中去竞争锁。

读写锁:https://segmentfault.com/a/1190000015768003

Semaphore&CountDownLatch&CyclicBarrier

Semaphore 信号量

信号量的意思,它的作用是控制访问特定资源的线程数目,底层依赖AQS的状态State。使用的是共享锁

public Semaphore(int permits) { // 一次允许通过的值,非公平锁
        sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) { // 公平锁
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}    

API

semaphore.acquire();// 支持中断的获取资源
semaphore.release();// 释放资源
semaphore.tryAcquire();// 支持超时处理
semaphore.acquireUninterruptibly();// 支持非中断的获取资源

CountDownLatch

够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
API

CountDownLatch.countDown();
ountDownLatch.await();

CyclicBarrier

栅栏屏障,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
API

CyclicBarrier.await();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值