并发编程02——synchronizedLockAQS详解

news/2024/7/2 22:58:58

目录

一、并发同步器-设计同步器的意义

Java锁体系:

二、synchronized使用与原理详解

Java线程生命状态:

2.1、synchronized使用示例:

2.2、synchronized底层原理

2.2.1、对象的内存布局

2.2.2、锁的膨胀升级过程

三、Lock

3.1、ReentrantLock源码

3.2、ReentrantReadWriterlock源码

四、AbstractQueuedSynchronizer(AQS)

juc并发编程包依赖于AQS的内部实现:

AQS框架-管理状态:

4.1、同步等待队列

4.2、条件等待队列

4.3、公平锁

4.4、非公平锁

4.5、重入锁

4.6、不可重入锁

4.7、读写锁


一、并发同步器-设计同步器的意义

多线程编程中,有可能会出现多个线程同时访问同一个共享可变资源的情况,这个资源我们称之其为临界资源;这种资源可能是:对象、变量、文件等。

共享:资源可以由多个线程同时访问。

可变:资源可以在其生命周期内被修改。

引出的问题: 由于线程执行的过程是不可控的,所以需要采用同步机制来协同对 对象可变状态的访问。

那么我们怎么解决线程并发安全问题?
 
实际上,所有的并发模式在解决线程安全问题时,采用的方案都是 序列化访问临界资源 。即在同一时刻,只能有一个线程访问临界资源,也称作 同步互斥访问
 
Java 中,提供了两种方式来实现同步互斥访问: synchronized Lock
 
同步器的本质就是加锁。
加锁目的: 序列化访问临界资源 ,即同一时刻只能有一个线程访问临界资源( 同步互斥访问)。
 
不过有一点需要区别的是:当多个线程执行一个方法时,该方法内部的局部变量不是临界资源,因为这些局部变量是在每个线程的私有栈中,因此不具有共享性,不会导致线程安全问题。
 

Java锁体系:

二、synchronized使用与原理详解

Java线程生命状态:

 
synchronized内置锁是一种 对象锁(锁的是对象而非引用。即使加在静态方法上成为类锁,类锁和一般对象锁也是互不干扰的。每个类只有一个类锁,类锁只是一个概念上的东西,并不是真实存在的。实际类锁使用时还是相当于是new了个对象,还是对象锁,但只都是同一个new对象,只有一个类锁。 ),作用粒度是对象,可以用来实现对临界资源的同步互斥访问,是可重入的。
加锁的方式:
  1. 同步实例方法,锁是当前实例对象;会锁住整个对象实例。
  2. 同步代码块,锁是括号里面的对象。只锁代码块。
  3. 同步类方法(加在static静态方法上),锁是当前类对象,类对象只有一个,即如果两个线程调用同一个类锁,是能锁住的;

2.1、synchronized使用示例:

1、synchronized直接修饰普通方法,会把整个实例锁住。如果是new两个实例,则互不干扰。

/**
* synchronized直接修饰普通方法,会把整个实例锁住。如果是new两个实例,则互不干扰。
*/
public class MyObject {

    //synchronized修饰为同步方法,如果先调用method1,则4秒后才会调用method2	
    private synchronized void method1(){
		try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	

	//如果不用synchronized修饰,则可以直接异步调用,没有影响
	private void method2(){
		System.out.println(Thread.currentThread().getName());
	}

    public static void main(String[] args) {
		//创建一个对象,t1走完才会走t2.
		MyObject myObject=new MyObject();

        Thread t1=new Thread (new Runnable() {
			@Override
			public void run() {
				myObject.method1();
			}
		},"t1");
		Thread t2=new Thread (new Runnable() {
			@Override
			public void run() {
				myObject.method1();
			}
		},"t2");
		t1.start();
		t2.start();

        //创建两个对象,t3和t1\t2互不干扰。
        MyObject myObject02=new MyObject();
        Thread t3=new Thread (new Runnable() {
			@Override
			public void run() {
				myObject02.method1();
			}
		},"t2");
		t3.start();
	}
}

2、synchronized修饰的代码块传入this也属于对象锁。但能减小锁粒度,只同步代码块。对于对象锁(this),如果是同一个实例,就会锁住、按顺序访问,但是如果是不同实例,就可以同时访问。多例的是锁不住的。

//如下,m1和m2锁的不是一个对象,互不干扰。且只锁代码块。this是当前对象。
class ThisLock2 {
    private final Object LOCK = new Object();
    
    public void m2() {
        synchronized (LOCK) {
            try {
                System.out.println(Thread.currentThread());
                Thread.sleep(10_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public void m1() {
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread());
                Thread.sleep(10_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3、类锁相关

package my.mark.mybaibaoxiang.concurrent.jmmAndVolatile.lock;

/**
 * @author twotiger-wxm.
 * 验证对象锁和类锁互不干扰,类锁只是一个概念上的东西,并不是真实存在的。
 * 就相当于下方示例中,
 * testClassLock.duixiangLock();和TestClassLock.classLock();
 * 调用的是两个实例。
 * 每个类只有一个类锁,多线程调用同一个类锁是能锁住的。
 */
public class TestClassLock {

    public static synchronized void classLock(){
        int i=0;
        while (i++<500){
            System.out.println("类锁=="+i+Thread.currentThread().getName());
            /*try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
        }
    }

    public synchronized void duixiangLock(){
        int i=0;
        while (i++<500){
            System.out.println("对象锁=="+i+Thread.currentThread().getName());
            /*try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
        }
    }

    public static void main(String[] args) {
        TestClassLock testClassLock = new TestClassLock();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                testClassLock.duixiangLock();
                //TestClassLock.classLock();//TODO 如果两个线程都用类锁,是能锁住的,执行完一个才会执行另一个。
            }
        },"t-duixiang");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                TestClassLock.classLock();
            }
        },"t-class");
        t1.start();
        t2.start();
    }
}

 

 

2.2、synchronized底层原理

 
        synchronized是基于JVM内置锁实现,JVM内置锁通过synchronized使用,通过内部对象 Monitor(监视器锁) 实现,基于进入与退出Monitor 对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的 Mutex lock(互斥锁) 实现,它是一个重量级锁性能较低。当然, JVM内置锁在1.5之后版本做了重大的优化, 如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight
Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销 ,内置锁的并发性能已经基本与Lock持平。
        synchronized关键字被编译成字节码后会被翻译成 monitorenter monitorexit 两条指令分别在同步块逻辑代码的起始位置与结束位置。
这个enter和exit对应JMM内存模型内存交互的八大原子操作的lock和unlock。
每个对象都有一个自己的Monitor( 管程,监视器锁 ),加锁过程如下图所示:
很多线程请求过来之后,会抢monitor,抢到的去执行。抢不到的进入waitSet等待队列。Exit释放锁释放monitor之后,通知队列里的去抢锁。
那么有个问题来了,我们知道synchronized加锁加在对象上,对象是如何记录锁状态的呢?
        答案是锁状态是被记录在每个对象的对象头(Mark Word)中,下面我们一起认识一下对象的内存布局 对象的内存布局。

2.2.1、对象的内存布局

Java内存区域:

 

HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
  1. 对象头:比如 hash码,对象所属的年代,对象锁,锁状态标志,偏向锁(线程)ID,偏向时间,数组长度(数组对象)等。
  2. 实例数据:即创建对象时,对象中成员变量,方法等。
  3. 对齐填充:对象的大小必须是8字节的整数倍。
实例对象内存中存储在哪?
如果实例对象存储在堆区时:实例对象内存存在堆区,实例的引用存在栈上,实例的元数据class存在方法区或者元空间。
但实例对象不一定是存在堆区的,如果实例对象没有线程逃逸行为,则会直接在栈上。参见逃逸分析。
 
new对象与类元数据比较:
1、每new一个对象都有。但32位和64位的对象头记录不一样。
2、元数据指针指向元空间的类。
3、new出来的对象跟元空间的类元数据,结构几乎是一样的。
 
对象头:
 
HotSpot虚拟机的 对象头 包括两部分信息, 第一部分是 “Mark Word ”,用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳 等等,这部分数据的长度在32位和
64位的虚拟机(暂不考虑开启压缩指针的场景)中分别为32个和64个Bits,官方称它为“Mark Word”。对象需要存储的运行时数据很多,其实已经超出了32、64位Bitmap结构所能记录的限度,但是对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内  存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如在32位的HotSpot虚拟机 中对象未被锁定的状态下,MarkWord的32个Bits空间中的25Bits用于存储对象哈希码(HashCode),4Bits用于存储对象分代年龄,2Bits用于存储锁标志位,1Bit固定为0,在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下表所示:
        但是如果对象是数组类型,则需要三个机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度。
 
Mark Word:
        对象头信息是与对象自身定义的数据无关的额外存储成本,但是考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的 数据结构 以便在极小的空间内存  存储尽量多的数据,它会根据对象的状态复用自己的存储空间,也就是说,Mark Word会随着程序的运行发生变化,变化状态如下(32位虚拟机):
以32位JVM中存储内容为例:她的锁有四种状态,由下往上进行膨胀升级。
 
jdk源码中说明:

2.2.2、锁的膨胀升级过程

JDK1.6版本之后对synchronized的实现进行了各种优化,如自旋锁、偏向锁和轻量级锁。并默认开启偏向锁。
开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
关闭偏向锁:-XX:-UseBiasedLocking
锁的状态总共有四种,无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级到重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。
JVM锁膨胀升级1:无锁到偏向锁到轻量级锁
 
JVM锁膨胀升级2:无锁-轻量级锁-重量级锁
 
下图为 锁的升级全过程
 
偏向锁
        偏向锁是Java 6之后加入的新锁,它是一种针对加锁操作的优化手段,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁(会涉及到一些CAS操作,耗时)的代价而引入偏向锁。偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。所以,对于没有锁竞争的场合,偏向锁有很好的优化效果,毕竟极有可能连续多次是同一个线程申请相同的锁。但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。下面我们接着了解轻量级锁。
 
轻量级锁
        倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的),此时Mark Word 的结构也变为轻量级锁的结构。轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。
 
自旋锁
        轻量级锁失败后,虚拟机为了避免线程 真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。这是基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,毕竟操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,因此自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这就是自旋锁的优化方式,这种方式确实也是可以提升效率的。
最后没办法也就只能升级为重量级锁了。
 
锁消除
        消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间,如StringBuffer的append是一个同步方法,但是在add方法中的StringBuffer属于一个局部变量,并且不会被其他线程所使用,因此StringBuffer不可能存在共享资源竞争的情景,JVM会自动将其锁消除。
 
逃逸分析
        使用逃逸分析,编译器可以对代码做如下优化:
、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。( synchronized作为对象锁,锁范围是同一个对象,两个对象之间是锁不住的。一般通过单例或者static锁成类锁来保证同一个对象。
、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。
、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。
 
        是不是所有的对象和数组都会在堆内存分配空间?
不一定
在Java代码运行时,通过JVM参数可指定是否开启逃逸分析, ­
-XX:+DoEscapeAnalysis : 表示开启逃逸分析 ;
- XX:­-DoEscapeAnalysis : 表示关 闭逃逸分析。
从jdk 1.7开始已经默认开启逃逸分析,如需关闭,需要指定 ­-XX:­ -DoEscapeAnalysis
关于逃逸分析的案例论证见如下Git源码百宝箱:
package my.mark.mybaibaoxiang.concurrent.jmmAndVolatile.lock;

/**
 * @author twotiger-wxm.
 * 逃逸分析验证
 */
public class StackAllocTest {

    /**
     * 进行两种测试
     * 关闭逃逸分析,同时调大堆空间,避免堆内GC的发生,如果有GC信息将会被打印出来
     * VM运行参数:-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
     *
     * 开启逃逸分析
     * VM运行参数:-Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
     *
     * 执行main方法后
     * jps 查看进程
     * jmap -histo 进程ID
     * 关闭逃逸分析的,显示有50万个实例。开启逃逸分析的只有八万多个。即开启逃逸分析很多对象没放堆。
     */

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000; i++) {
            alloc();
        }
        long end = System.currentTimeMillis();
        //查看执行时间
        System.out.println("cost-time " + (end - start) + " ms");
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
    }


    private static TulingStudent alloc() {
        //Jit(即时编译just-in-time)编译时会对代码进行 逃逸分析
        //并不是所有对象存放在堆区,有的一部分存在线程栈空间
        TulingStudent student = new TulingStudent();
        return student;
    }

    static class TulingStudent {
        private String name;
        private int age;
    }
}

三、Lock

以下代码相关见git百宝箱。

3.1、ReentrantLock源码

package my.mark.mybaibaoxiang.concurrent.jmmAndVolatile.lock.aqs;
/*
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

/*
 *
 *
 *
 *
 *
 * Written by Doug Lea with assistance from members of JCP JSR-166
 * Expert Group and released to the public domain, as explained at
 * http://creativecommons.org/publicdomain/zero/1.0/
 */

import java.util.Collection;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * A reentrant mutual exclusion {@link Lock} with the same basic
 * behavior and semantics as the implicit monitor lock accessed using
 * {@code synchronized} methods and statements, but with extended
 * capabilities.
 *
 * <p>A {@code ReentrantLock} is <em>owned</em> by the thread last
 * successfully locking, but not yet unlocking it. A thread invoking
 * {@code lock} will return, successfully acquiring the lock, when
 * the lock is not owned by another thread. The method will return
 * immediately if the current thread already owns the lock. This can
 * be checked using methods {@link #isHeldByCurrentThread}, and {@link
 * #getHoldCount}.
 *
 * <p>The constructor for this class accepts an optional
 * <em>fairness</em> parameter.  When set {@code true}, under
 * contention, locks favor granting access to the longest-waiting
 * thread.  Otherwise this lock does not guarantee any particular
 * access order.  Programs using fair locks accessed by many threads
 * may display lower overall throughput (i.e., are slower; often much
 * slower) than those using the default setting, but have smaller
 * variances in times to obtain locks and guarantee lack of
 * starvation. Note however, that fairness of locks does not guarantee
 * fairness of thread scheduling. Thus, one of many threads using a
 * fair lock may obtain it multiple times in succession while other
 * active threads are not progressing and not currently holding the
 * lock.
 * Also note that the untimed {@link #tryLock()} method does not
 * honor the fairness setting. It will succeed if the lock
 * is available even if other threads are waiting.
 *
 * <p>It is recommended practice to <em>always</em> immediately
 * follow a call to {@code lock} with a {@code try} block, most
 * typically in a before/after construction such as:
 *
 *  <pre> {@code
 * class X {
 *   private final ReentrantLock lock = new ReentrantLock();
 *   // ...
 *
 *   public void m() {
 *     lock.lock();  // block until condition holds
 *     try {
 *       // ... method body
 *     } finally {
 *       lock.unlock()
 *     }
 *   }
 * }}</pre>
 *
 * <p>In addition to implementing the {@link Lock} interface, this
 * class defines a number of {@code public} and {@code protected}
 * methods for inspecting the state of the lock.  Some of these
 * methods are only useful for instrumentation and monitoring.
 *
 * <p>Serialization of this class behaves in the same way as built-in
 * locks: a deserialized lock is in the unlocked state, regardless of
 * its state when serialized.
 *
 * <p>This lock supports a maximum of 2147483647 recursive locks by
 * the same thread. Attempts to exceed this limit result in
 * {@link Error} throws from locking methods.
 *
 * @since 1.5
 * @author Doug Lea
 * 悲观锁
 */
public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /**
     * 内部调用AQS的动作,都基于该成员属性实现
     */
    private final Sync sync;

    /**
     * ReentrantLock锁同步操作的基础类,继承自AQS框架.
     * 该类有两个继承类,1、NonfairSync 非公平锁,2、FairSync公平锁
     */
        abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * 加锁的具体行为由子类实现
         */
        abstract void lock();

        /**
         * 尝试获取非公平锁
         */
        final boolean nonfairTryAcquire(int acquires) {
            //acquires = 1
            final Thread current = Thread.currentThread();
            int c = getState();
            /**
             * 不需要判断同步队列(CLH)中是否有排队等待线程
             * 判断state状态是否为0,不为0可以加锁
             */
            if (c == 0) {
                //unsafe操作,cas修改state状态
                if (compareAndSetState(0, acquires)) {
                    //独占状态锁持有者指向当前线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            /**
             * state状态不为0,判断锁持有者是否是当前线程,
             * 如果是当前线程持有 则state+1
             */
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //加锁失败
            return false;
        }

        /**
         * 释放锁
         */
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        /**
         * 判断持有独占锁的线程是否是当前线程
         */
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        //返回条件对象
        final ConditionObject newCondition() {
            return new ConditionObject();
        }


        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
                throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

    /**
     * 非公平锁
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        /**
         * 加锁行为
         */
        final void lock() {
            /**
             * 第一步:直接尝试加锁
             * 与公平锁实现的加锁行为一个最大的区别在于,此处不会去判断同步队列(CLH队列)中
             * 是否有排队等待加锁的节点,上来直接加锁(判断state是否为0,CAS修改state为1)
             * ,并将独占锁持有者 exclusiveOwnerThread 属性指向当前线程
             * 如果当前有人占用锁,再尝试去加一次锁
             */
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //AQS定义的方法,加锁
                acquire(1);
        }

        /**
         * 父类AbstractQueuedSynchronizer.acquire()中调用本方法
         */
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    /**
     * 公平锁
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * 重写aqs中的方法逻辑
         * 尝试加锁,被AQS的acquire()方法调用
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                /**
                 * 与非公平锁中的区别,需要先判断队列当中是否有等待的节点
                 * 如果没有则可以尝试CAS获取锁
                 */
                if (!hasQueuedPredecessors() &&
                        compareAndSetState(0, acquires)) {
                    //独占线程指向当前线程
                    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;
        }
    }

    /**
     * 默认构造函数,创建非公平锁对象
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * 根据要求创建公平锁或非公平锁
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    /**
     * 加锁
     */
    public void lock() {
        sync.lock();
    }

    /**
     * 尝试获去取锁,获取失败被阻塞,线程被中断直接抛出异常
     */
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    /**
     * 尝试加锁
     */
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    /**
     * 指定等待时间内尝试加锁
     */
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    /**
     * 尝试去释放锁
     */
    public void unlock() {
        sync.release(1);
    }

    /**
     * 返回条件对象
     */
    public Condition newCondition() {
        return sync.newCondition();
    }

    /**
     * 返回当前线程持有的state状态数量
     */
    public int getHoldCount() {
        return sync.getHoldCount();
    }

    /**
     * 查询当前线程是否持有锁
     */
    public boolean isHeldByCurrentThread() {
        return sync.isHeldExclusively();
    }

    /**
     * 状态表示是否被Thread加锁持有
     */
    public boolean isLocked() {
        return sync.isLocked();
    }

    /**
     * 是否公平锁?是返回true 否则返回 false
     */
    public final boolean isFair() {
        return sync instanceof FairSync;
    }

    /**
     * Returns the thread that currently owns this lock, or
     * {@code null} if not owned. When this method is called by a
     * thread that is not the owner, the return value reflects a
     * best-effort approximation of current lock status. For example,
     * the owner may be momentarily {@code null} even if there are
     * threads trying to acquire the lock but have not yet done so.
     * This method is designed to facilitate construction of
     * subclasses that provide more extensive lock monitoring
     * facilities.
     *
     * @return the owner, or {@code null} if not owned
     */
    protected Thread getOwner() {
        return sync.getOwner();
    }

    /**
     * 判断队列当中是否有在等待获取锁的Thread节点
     */
    public final boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    /**
     * 当前线程是否在同步队列中等待
     */
    public final boolean hasQueuedThread(Thread thread) {
        return sync.isQueued(thread);
    }

    /**
     * Returns an estimate of the number of threads waiting to
     * acquire this lock.  The value is only an estimate because the number of
     * threads may change dynamically while this method traverses
     * internal data structures.  This method is designed for use in
     * monitoring of the system state, not for synchronization
     * control.
     *
     * @return the estimated number of threads waiting for this lock
     */
    public final int getQueueLength() {
        return sync.getQueueLength();
    }

    /**
     * 返回Thread集合,排队中的所有节点Thread会被返回
     */
    protected Collection<Thread> getQueuedThreads() {
        return sync.getQueuedThreads();
    }

    /**
     * 条件队列当中是否有正在等待的节点
     */
    public boolean hasWaiters(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    /**
     * Returns an estimate of the number of threads waiting on the
     * given condition associated with this lock. Note that because
     * timeouts and interrupts may occur at any time, the estimate
     * serves only as an upper bound on the actual number of waiters.
     * This method is designed for use in monitoring of the system
     * state, not for synchronization control.
     *
     * @param condition the condition
     * @return the estimated number of waiting threads
     * @throws IllegalMonitorStateException if this lock is not held
     * @throws IllegalArgumentException if the given condition is
     *         not associated with this lock
     * @throws NullPointerException if the condition is null
     */
    public int getWaitQueueLength(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    /**
     * Returns a collection containing those threads that may be
     * waiting on the given condition associated with this lock.
     * Because the actual set of threads may change dynamically while
     * constructing this result, the returned collection is only a
     * best-effort estimate. The elements of the returned collection
     * are in no particular order.  This method is designed to
     * facilitate construction of subclasses that provide more
     * extensive condition monitoring facilities.
     *
     * @param condition the condition
     * @return the collection of threads
     * @throws IllegalMonitorStateException if this lock is not held
     * @throws IllegalArgumentException if the given condition is
     *         not associated with this lock
     * @throws NullPointerException if the condition is null
     */
    protected Collection<Thread> getWaitingThreads(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
    }

    /**
     * Returns a string identifying this lock, as well as its lock state.
     * The state, in brackets, includes either the String {@code "Unlocked"}
     * or the String {@code "Locked by"} followed by the
     * {@linkplain Thread#getName name} of the owning thread.
     *
     * @return a string identifying this lock, as well as its lock state
     */
    public String toString() {
        Thread o = sync.getOwner();
        return super.toString() + ((o == null) ?
                "[Unlocked]" :
                "[Locked by thread " + o.getName() + "]");
    }
}

 

3.2、ReentrantReadWriterlock源码

见git百宝箱
 

四、AbstractQueuedSynchronizer(AQS)

并发之父 Doug Lea镇位
生平不识 Doug Lea ,学懂并发也枉然
        Java并发编程核心在于java.concurrent.util包,而juc当中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这个行为的抽象就是基于AbstractQueuedSynchronizer简称AQS,AQS定义了一套多线程访问共享资源的同步器框架,是一个依赖状态( state )的同步器。
AQS具备特性
阻塞等待队列
共享/独占
公平/非公平
可重入
允许中断
 
AQS中用了大量的死循环,循环中做线程的暂停中断。
独占也叫悲观,共享也叫乐观。
 

juc并发编程包依赖于AQS的内部实现:

例如Java.concurrent.util当中同步器的实现如Lock,Latch,Barrier等,都是基于AQS框架实现
  • 一般通过定义内部类Sync继承AQS。
  • 将同步器所有调用都映射到Sync对应的方法。

AQS框架-管理状态:

AQS内部维护属性 volatile int state (32位)
  • state表示资源的可用状态
State三种访问方式 getState()、setState()、compareAndSetState()
AQS定义两种资源共享方式
  • Exclusive-独占,只有一个线程能执行,如ReentrantLock
  • Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch
AQS定义两种队列
  • 同步等待队列
  • 条件等待队列
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现主要实现以下几种方法:
  1. isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  2. tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  3. tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  4. tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  5. tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

4.1、同步等待队列

AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明的一种基于双向链表数据结构的队列,是FIFO先入先出线程等待队列,Java中的CLH队列是原CLH队列的一个变种,线程由原自旋机制改为阻塞机制

4.2、条件等待队列

Condition是一个多线程间协调通信的工具类,使得某个,或者某些线程一起等待某个条件(Condition),只有当该条件具备时,这些等待线程才会被唤醒,从而重新争夺锁。
 
 
 

4.3、公平锁

公平和非公平锁的区别:获取锁失败时如何处理?

4.4、非公平锁

 

4.5、重入锁

 

4.6、不可重入锁

 

4.7、读写锁

写锁(独享锁、排他锁),是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得写锁的线程即能读数据又能修改数据。
 
读锁(共享锁)是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得读锁的线程只能读数据,不能修改数据。
 
AQS中state字段(int类型,32位),此处state上分别描述读锁和写锁的数量于是将state变量“按位切割”切分成了两个部分:
  1. 高16位表示读锁状态(读锁个数)
  2. 低16位表示写锁状态(写锁个数)

 

 

 

 

 

 

 


http://www.niftyadmin.cn/n/3032418.html

相关文章

代码保护软件VMProtect加壳脱壳原理总结

VMProtect是一种很可靠的工具&#xff0c;可以保护应用程序代码免受分析和破解&#xff0c;但只有在应用程序内保护机制正确构建且没有可能破坏整个保护的严重错误的情况下&#xff0c;才能实现最好的效果。 VMProtect通过在具有非标准体系结构的虚拟机上执行代码来保护代码&a…

ArcEngine+VC6二次开发之添加ESRI MapControl不支持此接口的解决办法

ArcEngineVC6二次开发 添加ESRI MapControl不支持此接口的解决办法 系统开发环境如下所示&#xff1a; ArcGIS Engine 9.3 RuntimeSDK VC6.0 一、创建一个VC工程 使用AppWizard来创建MFC工程(选择File->New->Project Workspace菜单&#xff0c; 这时弹出创建向导对话框&a…

FastReport VCL报表工具创建样式集教程

FastReport VCL是用于 Delphi、C Builder、RAD Studio 和 Lazarus 的报告和文档创建 VCL 库。它提供了可视化模板设计器&#xff0c;可以访问 30 多种格式&#xff0c;并可以部署到云、网站、电子邮件和打印中。 立即点击下载FastReport VCL v6.9最新版 以下代码演示了创建样…

HDU-1069-Monkey and Banana

链接&#xff1a;https://vjudge.net/problem/HDU-1069#authorprayerhgq 题意&#xff1a; 一组研究人员正在设计一项实验&#xff0c;以测试猴子的智商。他们将挂香蕉在建筑物的屋顶&#xff0c;同时&#xff0c;提供一些砖块给这些猴子。如果猴子足够聪明&#xff0c;它应当能…

长连接与短连接——JDK的HttpClient、ApacheHttpClient及OkHttpClient类比——Feign产品优化

目录 O、长连接与短链接 dubbo用长连接。 一、JDK的HttpClient 1.1、是否缓存复用是动态处理的&#xff1a; 1.2、HttpURLConnection、HttpClient、KeepAliveCache三个类的简单关系为&#xff1a; 1.3、链接缓存&#xff1a;继承自HashMap的实现。map的key也是特殊定义的…

wince 串口调试信息输出

不管在WinCE5.0还是在WinCE6.0中&#xff0c;我们在调试驱动或者应用的时候都会用到打印函数。在驱动里面&#xff0c;我们可能会用DEBUGMSG(..)&#xff0c;RETAILMSG(..)&#xff0c;还有NKDbgPrintfW(..)。在我们使用这些打印函数调试我们的程序之前&#xff0c;我们需要实现…

5. 内部类

内部类 在外部类中&#xff0c;内部类定义位置与外部类成员所处的位置相同&#xff0c;因此称为成员内部类。 1、实例内部类 即未被static修饰的成员内部类。 //外部类 class OuterClass {public int data1 1;private int data2 2;public static int data3 3;public Ou…

@Bean 注解

Configuration 以及其中的 Bean 注解 Configuration 注解: Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Component public interface Configuration {String value() default ""; } 从定义来看, Configuration 注解是用 Component 注解…