J.U.C之锁-互斥锁、阻塞锁、自旋锁、读写锁、ReentrantLock、公平锁与非公平锁原理、ReentrantLock与synchronized的区别、J.U.C之Condition

    -------小结-----------
1. Volatile 解决了什么问题
2. Volatile 能不能解决原子性问题
3. Volatile 怎么解决的这些问题,
4. synchroni zed和volatile比较
5.掌握CAS究竟是什么
6.需要知道如何实现的(自旋)
7.需要掌握guc报下的核心的原子类和常用的api
8. synchronized的实现原理
9. native关键字(JNI)
10.多CPU的CAS处理
11. cas的缺陷
    循环时间长
    ABA的问题
12. AQS是什么
13. AQS的核心实现原理
14.锁的基本概念-常识
15. ReentrantLock 可重入锁,
16. ReentrantLock与synchronized的区别-面试需要重点掌握
17.读写锁ReentrantReadWriteLock
高16为表示读,低16为表示写
18. Condition

J.U.C之锁
锁的基本概念
虽然在前面锁优化的部分已经提到过一些锁的概念 ,但不完全,这里是对锁的概念补充。

互斥锁
在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于-一个可称为" 互斥锁"的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

阻塞锁
阻塞锁,可以说是让线程进入阻塞状态进行等待,当获得相应的化号(η唤醒,时间)时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。

自旋锁
自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时,才能进入临界区。
由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。

读写锁
读写锁实际是一种特殊的自旋锁 ,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。
读写锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资
源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者 (与CPU数相关) ,但不能同时既有读者又有写者。

公平锁
公平锁(Fair ) :加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
非公平锁( Nonfair ) : 加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待
    非公平锁性能比公平锁高,因为公平锁需要在多核的情况下维护一个队列。


ReentrantLock
ReentrantLock ,可重入锁,是-种递归无阻塞的同步机制。它可以等同于synchronized的使用,但是
ReentrantLock提供了比synchronized更强大、灵活的锁机制,可以减少死锁发生的概率。
ReentrantLock还提供了公平锁和非公平锁的选择,构造方法接受一个可选的公平参数 (默认非公平锁),设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情下,公平锁表现出较低的吞吐量。
查看ReentrantLock源码中的构造方法:
public ReentrantLock() {
    //非公平锁
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    //公平锁
    sync = fair ? new FairSync() : new Nonfairsync(); 
}
    Sync为ReentrantLock里面的一个内部类,它继承AQS(AbstractQueuedSyschronizer),它有两个子类:公平锁FairSync和非公平锁NonfairSync


获取锁
-般都是这么使用ReentrantLock获取锁的: (默认非公平锁)

    //非公平锁
    ReentrantLock lock = new ReentrantLock();
    lock. lock(); 
    
    lock方法:
    public void lock() {
        sync.1ock();
    }

加锁最终可以看到会调用方法:
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addwaiter(Node. EXCLUSIVE),arg))
        selfInterrupt();

其实底层就是使用AQS同步队列。


释放锁
获取同步锁后,使用完毕则需要释放锁. ReentrantLock提供了unlock释放锁:
    public void unlock() {
        sync.release(1);
    }

unlock内部使用Sync的release(释放锁, release()是在AQS中定义的:
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus !=0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

释放同步状态的tryRelease()是同步组件自己实现:
protected final boolean tryRelease(int releases) {
    //减掉releases
    int C = getState() - releases;
    //如果释放的不是持有锁的线程,抛出异常
    if (Thread. currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //state ==日表示已经释放完全了。其他线程可以获取同步状态了
    if (C== 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(C);
    return free;
}
只有当同步状态彻底释放后该方法才会返回true。当同步队列的状态state == 0时,则将锁持有线程设置为null, free= true ,表示释放成功。

 

 

公平锁与非公平锁原理
公平锁与非公平锁的区别在于获取锁的时候是否按照FIFO的顺序来。释放锁不存在公平性和非公平性,比较非公平锁和公平锁获取同步状态的过程,会发现两者唯一的区别就在于公平 锁在获取同步状态时多了一个限制条件: hasQueuedPredecessors(),定义如下:
public final boolean hasQueuedPredecessors() {
    Node t = tail; //尾节点
    Node h = head; //头节 点
    Node s;

    //头节点1-尾节点
    //同步队列第一一个节点不为null
    //当前线程是同步队列第一个节点
    return h != t &&
        ((S = h.next) == nu1l|I s. thread I= Thread. currentThread());
}

该方法主要做一件事情:主要是判断当前线程是否位于CLH同步队列中的第一个。 如果是则返回true ,否则返回false.

 

 

ReentrantLock与synchronized的区别
前面提到ReentrantLock提供了比synchronized更加灵活和强大的锁机制.那么它的灵活和强大之处在哪里呢?他们之间又有什么相异之处呢?
1.与synchronized相比, ReentrantLock提供了更多.更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。
2. ReentrantLock还提供了条件Condition ,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方, ReentrantLock更加适合(以后会阐述Condition )。
3. ReentrantLock提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而synchronized则- -旦进入锁请求要么成功要么阻塞,所以相比synchronized而言, ReentrantLock会不
容易产生死锁些。
4. ReentrantLock支持更加灵活的同步代码块,但是使用synchronized时,只能在同一个synchronized块结构中获取和释放。注: ReentrantLock的锁释放-定要在finally中处理 ,咨则可能会产生严重的后果。
5. ReentrantLock支持中断处理,且性能较synchronized会好些。

 

 

读写锁ReentrantReadWriteLock
可重入锁ReentrantLock是互斥锁,互斥锁在同-一时刻仅有一个线程可以进行访问 ,但是在大多数场景下,大部分时间都是提供读服务,而写服务占有的时间较少。然而读服务不存在数据竞争问题,如果一一个线程在读时禁止其他线程读势必会导致性能降低。所以就提供了读写锁。

读写锁维护着一对锁,一个读锁和一个写锁。通过分离读锁和写锁,使得并发性比一般的互斥锁有了较大的提升;在同一时间可以允许多个读线程同时访问,但是在写线程访问时,所有读线程和写线程都会被阻塞。

读写锁的主要特性: 
    1.公平性:支持公平性和非公平性。
    2.重入性:支持重入。读写锁最多支持65535个递归写入锁和65535个递归读取锁。
    3.锁降级:写锁能够降级成为读锁,遵循获取写锁、获取读锁在释放写锁的次序。读锁不能升级为写锁。    获取写锁—>获取读锁—>释放写锁

读写锁ReentrantReadWriteLock实现接口ReadWriteLock ,该接口维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有writer ,读取锁可以由多个reader线程同时保持。写入锁是独占的。
public interface ReadwiriteLock {
    Lock readLock();
    Lock writeLock();

ReadWriteLock定义了两个方法。readLock()返回用于读操作的锁, writeLock()返回用于写操作的锁。

ReentrantReadWriteLock与ReentrantLock一样,其锁主体依然是Sync 它的读锁、写锁都是依靠Sync来实现的。所以ReentrantReadWriteLock实际 上只有一个锁,只是在获取读取锁和写入锁的方式上不一样而已,它的读写锁其实就是两个类: ReadLock 、writeLock ,这两个类都是lock实现。

在ReentrantLock中使用一个int类型的state来表示同步状态,该值表示锁被一个线程重复获取的次数。但是读写锁ReentrantReadWriteLock内部维护着一对锁, 需要用一个变量维护多种状态。所以读写锁采用“按位切割使用”的方式来维护这个变量,将其切分为两部分,高16为表示读,低16为表示写。分割之后,读写锁是如何迅速确定读锁和写锁的状态呢?通过位运算。假如当前同步代态为S ,那么写状态等于 S & 0x0000FFFF (将高16位全部抹去) , 读状态等于S >>> 16(无符号补0右移16位)。

 

 

J.U.C之Condition
Condition介绍
在没有Lock之前,我们使用synchronized来控制同步,配合Object的wait()、notify()系列方法可以实现等待/通知模式。在JDK5后 , Java提供了Lok接口,相对于Synchronized而言, Lock提供了条件Condition ,对线程的等待、唤醒操作更加详细和灵活。


下图是Condition与Object的监视器方法的对比:
对比项                Object监视器方法            Condition
前置条件                获取对象的锁        调用Lock. lock()获取锁
                          调用Lock, nenCondtion()获取Condition对象
调用方式                直接调用                直接调用
等待队列个数              一个                  多个
当前线程释放锁并进入等待状态      支持                  支持
当前线程释放锁并进入等待状态      不支持               支持
,在等待状态中不响应中断

当前线程释放锁并进入超时等待状态      支持                  支持

当前线程释放锁并进入从等待      不支持                  支持
    状态到将来的某个时间

唤酲等待队列中帕某个线程          支持                  支持

唤醒等待队列中的全部线程          支持                  支持


Condition提供了一系列的方法来对阻塞和唤醒线程 :
1. await() :造成当前线程在接到信号或被中断之前一直处于等待状态。
2. await(long time, TimeUnit unit) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
3. awaitNanos(long nanosTimeout) : 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值= nanosTimeout -消耗时间,如果返回值<= 0 ,则可以认定它已经超时了。
4. awaitUninterruptibly() : 造成当前线程在接到信号之前一-直处于等待状态。[注意 :该方法对中断不敏感]。
5. awaitUntil(Date deadline) : 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回返回false。
6. signal(): 唤醒一个等待线程。 该线程从等待方法返回前必须获得与Condition相关的锁。
7. signal()AII :唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁。
Condition是一种广义上的条件队列(等待队列)。他为线程提供了一种更为灵活的等待/通知模式,线程在调用await方法后执行挂起操作.直到线程等待的某个条件为真时才会被唤醒。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定 ,因此Condition一般都是作为Lock的内部实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值