java基础| 多线程基础四:显示锁详解

  • 显示锁

        只要实现了Lock接口的锁都叫显示锁锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,比如读写锁)。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而Java SE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性。使用synchronized关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。当然,这种方式简化了同步的管理,可是扩展性没有显示的锁获取和释放来的好。例如,针对一个场景,手把手进行锁获取和释放,先获得锁A,然后再获取锁B,当锁B获得后,释放锁A同时获取锁C,当锁C获得后,再释放B同时获取锁D,以此类推。这种场景下,synchronized关键字就不那么容易实现了,而使用Lock却容易许多。

  • Lock接口详解

                                                                                    Lock接口及继承关系类

        LockReadWriteLock是两大锁的根接口,Lock代表实现类是ReentrantLock(可重入锁),ReadWriteLock(读写锁)的代表实现类是ReentrantReadWriteLock。其中: Lock 接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。ReadWriteLock 接口以类似方式定义了一些读取者可以共享而写入者独占的锁。此包只提供了一个实现,即 ReentrantReadWriteLock,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的、适用于非标准要求的实现。

        Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。

  • synchronized与Lock的区别
synchronized与Lock的区别
类别synchronizedLock
存在层次Java的关键字,在jvm层面上代码层面,是一个类
锁的释放1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁在finally中必须释放锁,不然容易造成线程死锁
锁的获取假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态无法判断可以判断
锁类型可重入 不可中断 非公平可重入 可判断 可公平(两者皆可)
性能少量同步大量同步

什么是可重入性?

        所谓的可重入性,就是 可以支持一个线程对锁的重复获取 ,原生的 synchronized 就具有可重入性,一个用synchronized 修饰的递归方法,当线程在执行期间,它是可以反复获取到锁的,而不会出现自己把自己锁死的情况。ReentrantLock 也是如此,在调用 lock() 方法时,已经获取到锁的线程,能够再次调用lock() 方法获取锁而不被阻塞。
 

什么是公平锁(安全锁)和非公平锁(非安全锁)?

        所谓公平锁 , 顾名思义,意指锁的获取策略相对公平,当多个线程在获取同一个锁时,必须按照锁的申请时间(申请队列)来依次获得锁,排排队,不能插队非公平锁则不同,当锁被释放时,等待中的线程均有机会获得锁(随机线程获得锁)。synchronized 是非公平锁, ReentrantLock 默认也是非公平的,但是可以通过带 boolean参数的构造方法指定使用公平锁,但非公平锁的性能一般要优于公平锁。 synchronized Java 原生的互斥同步锁,使用方便,对于 synchronized 修饰的方法或同步块,无需 再显式释放锁。而 ReentrantLock 做为 API 层面的互斥锁,需要显式地去加锁解锁。采用 Lock ,必须主 动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用 Lock 必须在 try{}catch{} 块中 进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被被释放,防止死锁的发生。
 
公平锁和非公平锁的区别:
  1. 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
  2. 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列 是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。如果两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
  • Lock接口源码如下:
public interface Lock {

    /**
     * Acquires the lock.
     */
    void lock();

    /**
     * Acquires the lock unless the current thread is
     * {@linkplain Thread#interrupt interrupted}.
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * Acquires the lock only if it is free at the time of invocation.
     */
    boolean tryLock();

    /**
     * Acquires the lock if it is free within the given waiting time and the
     * current thread has not been {@linkplain Thread#interrupt interrupted}.
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     * Releases the lock.
     */
    void unlock();
	
}

Lock类可以创建Condition对象,Condition对象是用来线程等待和线程唤醒的,需要注意的是Condition对象的唤醒的是用同一个Condition执行await方法的线程,所以也就可以实现唤醒指定类的线程

 
ReentrantLock 锁的使用
ReentrantLock Java 并发包中提供的一个 可重入的互斥锁 ReentrantLock synchronized 在基本用法,行为语义上都是类似的,同样都具有可重入性。只不过相比原生的Synchronized ,ReentrantLock增加了一些高级的扩展功能,比如它可以实现 公平锁, 同时也可以绑定 多个 Conditon
 
ReentrantLock函数原型:(ReentrantLock默认采用的是非公平锁机制,通过设置参数可以设置为公平锁)
public ReentrantLock() { 
    sync = new NonfairSync(); 
}

public ReentrantLock(boolean fair //传入参数设置是否是公平锁) { 
    sync = fair ? new FairSync() : new NonfairSync(); 
}


//公平锁的函数原型
/*** Sync object for fair locks */ 
static final class FairSync extends Sync { 
    private static final long serialVersionUID = -3000897897090466540L; 
    final void lock() { 
        acquire(1); 
    }
    /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no                 
          waiters or is first. 
    */ 
    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; 
    } 
}

//非公平锁函数原型
/*** Sync object for non-fair locks */ 
static final class NonfairSync extends Sync { 
    private static final long serialVersionUID = 7316153563782823691L; 
    /*** Performs lock. Try immediate barge, backing up to normal * acquire on failure.     
    */
     final void lock() { // 和公平锁相比,这里会直接先进行一次CAS,成功就返回了 
        if (compareAndSetState(0, 1)) 
            setExclusiveOwnerThread(Thread.currentThread()); 
        elseacquire(1); 
    }
    protected final boolean tryAcquire(int acquires) { 
        return nonfairTryAcquire(acquires); 
    } 
}

/*** Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ 
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;
}

syncReentrantLock内部实现的一个同步组件,它是Reentrantlock的一个静态内部类,继承于AQS。FailSyncNonFailSync亦为Reentrantlock的静态内部类,都继承Sync

ReentrantLock的应用:

import java.util.concurrent.locks.Lock; 
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo { 
    private Lock lock = new ReentrantLock(); 
    Integer count = 0; 
    public void add(){ 
        lock.lock(); 
        try{
            count++; 
        }finally { 
            lock.unlock(); 
        } 
    }

    public int getCount() { 
        return count; 
    }//线程 

    private static class CountAdd extends Thread{ 
        private ReentrantLockDemo simp; 
        public CountAdd(ReentrantLockDemo simp) { 
            this.simp = simp; 
        }

        @Override 
        public void run() { 
            for(int i=0;i<10000;i++){ 
                simp.add(); 
            } 
        } 
    }

    public static void main(String[] args) throws InterruptedException{ 
        ReentrantLockDemo demo = new ReentrantLockDemo(); 
        CountAdd count1 = new CountAdd(demo); 
        CountAdd count2 = new CountAdd(demo); 
        CountAdd count3 = new CountAdd(demo); 
        CountAdd count4 = new CountAdd(demo); 
        count1.start(); 
        count2.start(); 
        count3.start(); 
        count4.start(); 
        Thread.currentThread().sleep(100); 
        System.out.println("count进行多线程相加后的结果:"+demo.getCount()); 
    } 
}
  • ReentrantReadWriteLock锁(可重入读写锁)
        对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作。
       针对这种场景, JAVA 的并发包提供了读写锁 ReentrantReadWriteLock ,它表示两个锁,一个是读操 作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁
 
线程进入读锁的前提条件:
  1. 没有其他线程的写锁;
  2. 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个;
线程进入写锁的前提条件:
  1. 没有其他线程的读锁
  2. 没有其他线程的写锁
而读写锁有以下三个重要的特性:
  1. 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
  2. 重进入:读锁和写锁都支持线程重进入。
  3. 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
  4. 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值