天天看点

Java基础-锁Lock基本实现关键字:synchronized接口:Lock实现类:ReentrantLock

基本实现

Lock与ReadWriteLock是两大锁根接口

(1)Lock :

Lock 接口支持那些语义不同(重入、公平等)的锁规则;

可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。

主要实现类是ReentrantLock(可重入锁)

(2)ReadWriteLock(读写锁):

以Lock类似方式定义了一些读取者可以共享而写入者独占的锁。

主要实现类是ReentrantReadWriteLock

(3)Condition:

描述了可能会与锁有关联的条件变量。

单个 Lock 可能与多个 Condition 对象关联

关键字:synchronized

synchronized是java中的一个关键字,也就是说是Java语言内置的特性。

Lock不是Java语言内置的,Lock是一个类,通过这个类可以实现同步访问。

Lock和synchronized有一点非常大的不同:

synchronized不需要用户去手动释放锁,系统会自动让线程释放对锁的占用。

而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

局限性

如果一个代码块被synchronized关键字修饰;

当一个线程获取了对应的锁,并执行该代码块时;

其他线程便只能一直等待直至占有锁的线程释放锁。

在使用synchronized关键字的情形下,可能因为某些原因,无期限阻塞下去。

当多个线程读写文件时,读操作和读操作不会发生冲突。采用synchronized关键字实现同步的话,多线程进行读操作,也只能一个进行读操作,其他线程只能等待。

Lock 实现提供了比 synchronized 关键字 更广泛的锁操作,它能以更优雅的方式处理线程同步问题。

接口:Lock

(1)接口介绍

Lock接口有6个方法:

// 获取锁  
void lock() ;  

// 如果当前线程未被中断,则获取锁,可以响应中断  
void lockInterruptibly() ; 

// 返回绑定到此 Lock 实例的新 Condition 实例  
Condition newCondition()  ;  

// 仅在调用时锁为空闲状态才获取该锁,可以响应中断  
boolean tryLock() ;

// 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁  
boolean tryLock(long time, TimeUnit unit) ;  

// 释放锁  
void unlock();           

(2)方法:lock()

lock()方法是平常使用得最多的一个方法,就是用来获取锁。

如果锁已被其他线程获取,则进行等待。

如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。

一般来说,使用Lock必须在try…catch…块中进行;

并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。

例如:

lock.lock();
try {
    inc++; 
} finally {
    lock.unlock();
}           

(3)方法:tryLock() & tryLock(long time, TimeUnit unit)

tryLock()方法是有返回值的,它表示用来尝试获取锁;

如果获取成功,则返回true;

如果获取失败(即锁已被其他线程获取),则返回false。

这个方法无论如何都会立即返回(在拿不到锁时不会一直在那等待)。

tryLock(long time, TimeUnit unit) 类似

在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,同时可以响应中断。

如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

if(lock.tryLock()) {
    try{
       //处理任务
    }catch(Exception ex){ }
    finally{
       lock.unlock();   //释放锁
    }
}else {
    //如果不能获取锁,则直接做其他事情
}           

(4)方法:lockInterruptibly()

当通过这个方法去获取锁时,如果线程 正在等待获取锁,则这个线程能够 响应中断,即中断线程的等待状态。

lock.lockInterruptibly()必须放在try块中;

或者在调用lockInterruptibly()的方法外声明抛出 InterruptedException

当一个线程获取了锁之后,是不会被interrupt()方法中断的。

因为interrupt()方法只能中断阻塞过程中的线程而不能中断正在运行过程中的线程。

当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,那么只有进行等待的情况下,才可以响应中断的。

与 synchronized 相比,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

实现类:ReentrantLock

ReentrantLock,即 可重入锁。

ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。

ReentrantLock构造方法(不带参数 和带参数 true: 公平锁; false: 非公平锁)

注意:

(1)synchronized是Java语言的关键字,因此是内置特性;Lock不是Java语言内置的,Lock是一个接口,通过实现类可以实现同步访问。

(2)synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。

(3)在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock;但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态。

如下: 公平锁会按顺序输出,非公平锁不一定按顺序输出

static Lock lock = new ReentrantLock(true);
static void lock(String name) {
    lock.lock();
    try {
        System.out.println(name + " get the lock");
    } finally {
        System.out.println(name + " release the lock");
        lock.unlock();
    }
}

public static void main(String[] args) {
    new Thread(() -> lock("0")).start();
    new Thread(() -> lock("1")).start();
    new Thread(() -> lock("2")).start();
    new Thread(() -> lock("3")).start();
    new Thread(() -> lock("4")).start();
    new Thread(() -> lock("5")).start();
    new Thread(() -> lock("6")).start();
    new Thread(() -> lock("7")).start();
    new Thread(() -> lock("8")).start();
    new Thread(() -> lock("9")).start();
}           

非公平锁里

判断当前锁占用状态==0直接会进行compareAndSetState尝试获取锁

若此时有线程排队,可能争夺不过资源。

非公平锁里,不需要加入队列、等待队列头线程唤醒再获取锁这一步骤,所以效率较快。

公平锁里

判断当前锁占用状态==0后

会继续判断hasQueuedPredecessors,即当前队列是否有排队的情况,如果没有才会尝试获取锁

这样可以保证遵循FIFO的原则,每一个先来的线程都可以最先获取到锁。

但是增加了上下文切换与等待线程的状态变换时间。所以效率相较于非公平锁较慢。