[Java并发系列]Java中的锁

讨论J.U.C包中locks下面的类(包括接口)

锁主要是用来控制多个线程访问共享资源的一种方式,通常情况下,一个锁可以防止在同一时间内多个线程同时访问共享资源(读写锁除外,读写锁在同一时间内,可以允许有多个读锁同时读共享资源)。

1. Lock接口

Lock接口同synchronized关键字的作用类似,都是提供了同步的功能。但是Lock在使用的时候,需要显式的去获取锁。与synchronized相比,Lock失去了隐式获取锁的便捷性,但是可以控制锁的获取和释放,可中断锁和超时锁。

2. Lock接口主要API

  • void lock(); 获取锁
  • void lockInterruptibly(); 可中断的获取锁,此方法和lock()方法的区别在于: 当使用lockInterruptibly获取锁时可以中断当前线程;
  • boolean tryLock(); 尝试非阻塞的获取锁,当调用此方法之后,如果能获取则返回true,如果不能获取则直接返回false;
  • boolean tryLock(long time, TimeUnit unit); 超时获取锁
  • void unlock(); 释放锁
  • Condition newCondition(); 获取等待通知组件,该组件和当前的锁绑定在一起,只有当前线程获的了锁,才能调用该组件的wait()方法,调用wait()方法之后,当前线程将释放锁;

3. Lock接口的实现类

1. ReentrantLock(重入锁)

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

重进入: 指的是任意线程在获取锁能够再次获取该所而不会被阻塞。 公平与非公平获取锁:公平指的是在绝对时间上,先对锁进行请求的线程(等待时间最长的线程优先获取锁)首先获取锁,那么这个锁是公平的,反之,则是非公平的。

①. 锁的重进入

如果要实现锁的重进入,那么就就绪解决两个问题:

  • 锁的获取:要获取锁,那么锁就需要去检查获取该锁的线程是否是已获取此锁的线程(也就是是否是当前线程占有此锁),如果是,那么获取成功;如下代码是非公平获取锁的方式
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;
        }

在此方法中,首先判断此锁是否已被占有,如果没有则使用CAS的方式设置同步状态;如果锁已被占有,则判断当前线程是否是占有此锁的线程,然后再来决定获取操作是否成功,如果获取锁的线程再次请求获取锁,则将同步状态值进行增加并且返回true。 所以重入锁的获取就是当线程重入成功,增加锁的同步状态值即可。

  • 锁的释放:线程重复N此获取锁,那么就需要释放N次,其他的线程才可以获取该锁。如下代码是释放锁的代码:
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;
        }

如果一个锁被某个线程获取了N次,那么前(N-1)次都会返回false,而当同步状态完成被释放时(c=0),将占有线程设置为null,才会返回true。

②. 公平与非公平的获取锁

如下是公平获取锁的代码:

protected final boolean tryAcquire(int acquires) {            
            final Thread current = Thread.currentThread();            
            int c = getState();            
            if (c == 0) {                
                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;
        }
    }

与上面非公平获取锁的代码相比,在这段代码中,仅仅在if条件中多了一个hasQueuedPredecessors()方法,此方法就是判断在同步队列中,当前节点是否有前驱节点(即有比当前线程更早的获取锁的线程),因此当hasQueuedPredecessors()返回true时,就需要等待前驱线程获取并释放锁之后才能继续获取锁。

2. ReadWriteLock(读写锁)

排他锁:指的是在同一时刻只允许一个线程进行访问 读写锁:在同一时间,允许有多个读线程进行访问,而在写线程进行访问时,读线程和其他写线程均会被阻塞。读写锁维护了一个读锁和一个写锁,通过读写分离,来提升并发性能(至少比排他锁性能好多了)。

//todo 读写锁内容较多,留待以后来写

4. LockSupport类

LockSupport类位于在J.U.C.locks包中,它主要是定义了一些公共静态方法,这些方法提供了最基本的线程阻塞和唤醒功能。如下表是LockSupport中提供的一些方法及描述:

方法

描述

public static void park()

阻塞当前线程,当其他线程调用unpark()或者中断当前线程时,才能从park()方法返回

fipublic static void parkNanos(long nanos)

阻塞当前线程,超过nanos纳秒之后,自动返回

public static void parkUntil(long deadline)

阻塞当前线程,直到deadline时间

public static void unpark(Thread thread)

唤醒处于阻塞的线程

原文发布于微信公众号 - 瞎说开发那些事(jsj201501)

原文发表时间:2017-10-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏云霄雨霁

Java--线程同步&线程通信

1987
来自专栏我是攻城师

关于自旋锁的公平和非公平模式

自旋锁是并发编程实战里面一个关于锁优化的非常重要的一个概念,通常情况下会配合CAS原语来实现轻量级的同步操作。

682
来自专栏Linyb极客之路

并发编程之各种锁的简介

一、公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁。 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优...

3546
来自专栏古时的风筝

Java多线程之---用 CountDownLatch 说明 AQS 的实现原理

前面的文章中说到了 volatile 以及用 volatile 来实现自旋锁,例如 java.util.concurrent.atomic 包下的工具类。但是 ...

800
来自专栏陈树义

Java并发编程:同步锁、读写锁

之前我们说过线程安全问题可以用锁机制来解决,即线程必要要先获得锁,之后才能进行其他操作。其实在 Java 的 API 中有这样一些锁类可以提供给我们使用,与其他...

4177
来自专栏微信公众号:Java团长

Java 并发开发:Lock 框架详解

我们已经知道,synchronized 是java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度...

662
来自专栏小灰灰

Java并发学习之CountDownLatch实现原理及使用姿势

CountDownLatch实现原理及使用姿势 在并发编程的场景中,最常见的一个case是某个任务的执行,需要等到多个线程都执行完毕之后才可以进行,Count...

1.3K8
来自专栏Golang语言社区

锁的优化和注意事项151

1. 锁优化的思路和方法 在[高并发Java 一] 前言中有提到并发的级别。 一旦用到锁,就说明这是阻塞式的,所以在并发度上一般来说都会比无锁的情况低一点。 这...

36711
来自专栏JMCui

多线程编程学习四(Lock 的使用)

一、前言     本文要介绍使用Java5中 Lock 对象,同样也能实现同步的效果,而且在使用上更加方便、灵活,主要包括 ReentrantLock 类的使用...

35112
来自专栏大闲人柴毛毛

Java并发编程的艺术(十三)——锁优化

自旋锁 背景:互斥同步对性能最大的影响是阻塞,挂起和恢复线程都需要转入内核态中完成;并且通常情况下,共享数据的锁定状态只持续很短的一段时间,为了这很短的一段时...

3315

扫码关注云+社区