锁是用来控制多个线程访问共享资源的方式,一个锁能够防止多个线程同时访问共享资源。

Lock

在**Lock接口出现之前,Java通过synchronized关键字实现锁功能,JDK 1.5之后,在并发包中新增了Lock接口来实现锁功能,有与synchronized关键字类似功能,只是在使用时需要显示的获取锁和释放锁,拥有了锁释放和获取的可操作性、可中断性等synchronized关键字不具备的特性。在使用synchronized关键字会隐式的获取锁和释放锁,但锁的获取和释放固化,即先获取后释放。Lock接口的实现基本上是通过聚合一个同步器(AQS**)的子类来完成线程访问控制。

Lock接口的使用方式:

Lock lock = new ReentrantLock();
lock.lock();
try{
}finally{
	lock.unlock()
}

比较synchronized和ReentrantLock

  • 锁的实现
    synchronized是JVM实现的,而ReentrantLock是JDK实现
  • 性能新版Java对synchronized进行了很多的优化,synchronized与ReentrantLock大致相同。
  • 等待可中断
    当持有锁的线程长期不释放锁,正在等待的线程可以选择放弃等待,改为处理其他事情。ReentrantLock可以中断,而synchronized不可以中断
  • 公平锁
    公平锁是指多个线程在等待同一锁时,必须按照申请的时间顺序来依次获得锁。synchronized中的锁是非公平的,ReentrantLock默认也是非公平,但也是可以公平的
  • 锁绑定多个条件
    一个ReentrantLock可以同时绑定多个Condition对象

如何选择呢?

优先使用synchronized,除非需要使用ReentrantLock的高级功能,因为synchronized是JVM实现的一种锁机制,JVM原生的支持,而ReentrantLock不是所有的JDK都支持,synchronized不用担心没有锁而造成死锁问题,JVM会确保锁的释放。
上面的代码示例中要注意:获取锁的过程不要写在**try**中,因为在获取锁时发生异常,异常抛出的同时,也会导致锁无故释放。

重入锁

重入锁ReentrantLock,支持重进入的锁,表示该锁功能支持一个线程对资源的重复加锁。

实现重进入

重进入是指任意线程在获取锁之后能够再次获取该锁而不会被锁所阻塞,这个特性需要解决两个问题:

  • 线程再次获取锁:所需要去识别获取锁的线程是否为当前占据锁的线程,如果是则再次成功获取。
  • 锁的最终释放:线程重复n次获取锁,随后第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减当计数等于0时表示已经释放成功。

重入锁是通过组合自定义**同步器**来实现锁的获取和释放。获取同步状态的代码如下:

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            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;
        }

通过判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。

读写锁

读写锁维护一对锁,一个读锁和一个写锁,通过分离读锁和写锁使得并发性相比一般的排他锁有了更大的提升,读写锁的性能都会比排它锁好,因为大多数场景读操作对于写操作,在这样的情况下读写锁能够提供排它锁更好的并发性和吞吐量,

java lock锁哪个对象 java锁作用_java

读写锁的实现

  • 读写状态的设计
    读写锁同样依赖自定义同步器来实现同步功能,而读写状态就是其同步器的同步状态。读写锁的自定义同步器需要在同步同步状态上维护多个读线程和一个写线程的状态,状态的设计成为读写锁实现的关键。
    如果在一个整型变量上维护多种状态,就一定需要“按位切割使用”这个变量,读写锁将变量切分成两个部分,高16为表示读,低16位表示写
  • 写锁的获取与释放
    写锁是一个支持重进入的排它锁,如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。写锁的释放与ReentrantLock的释放过程基本相似,每次释放均减少写状态,当写状态为0时表示写锁已经被释放,从而等待读写线程能够继续访问读写锁,同时前次的写线程的修改对后续读写线程可见。
  • 读锁的获取与释放
    读锁是一个支持重进入的共享锁,能够被多个线程同时获取,在没有其他的写线程访问时,读锁总会被成功的获取,而所做的只是增加读状态,如果当前线程已经获取了读锁,则增加读状。如果当前线程在获取读锁时,写锁已被其他线程获取,则进入等待状态。
  • 锁降级
    锁降级指的是写锁降级为读锁。如果当前线程拥有写锁,然后将其释放,最后在获取读锁,这种分段完成的过程不能称为锁降级。锁降级是指把持住写锁,再获取到读锁,随后释放写锁的过程。锁降级中读锁释放主要是为了保证数据的可见性。

队列同步器

队列同步器**AbstractQueuedSynchronizer,是用来构建锁或者其他同步组件的基本框架,使用一个int**成员变量表示同步状态,通过内置FIFO队列来完成资源的获取线程的排队工作。

java lock锁哪个对象 java锁作用_JVM_02

同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。二者的关系是:锁是面向使用者,定义了使用者与锁交互的接口,隐藏了实现细节;同步器则面向锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步器状态。

队列同步器的实现分析

同步队列

同步器依赖内部的同步队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态的信息够造成一个节点将其加入到同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步器状态。

节点是构成同步队列的基础,同步器拥有首节点和尾节点,没有成功获取同步状态的线程会成为节点加入到该队列的尾部。