Lock锁

我们在介绍Lock锁之前先介绍锁的几个概念。

1. 可重入锁

如synchronized和ReentrantLock都是可重入锁,可重入性实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。当一个线程执行到某个synchronized方法时,如method1方法,而在method1中会调用另一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行method2。如递归方法的调用,如果递归方法被synchronized修饰,如果synchronized不是可重入的,这样就会出现自己等待自己释放锁的情况。

2. 可中断锁

就是可以响应中断的锁,在Java中,synchronized是不可中断锁,而Lock是可中断锁。

如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他的事情,我们可以让它中断自己或在别的线程中中断它,这就是可中断锁。

3. 公平锁

公平锁尽量以请求锁的顺序来获取锁,如多个线程同时在等待一个锁,当这个锁被释放后,等待时间最长的线程(即最先请求锁的线程)会获得锁,这就是公平锁。

非公平锁无法保证锁的获取是按照锁的顺序进行的。这样就可能导致某个或者一些线程长时间或永远获取不到锁。

在Java中,synchronized就是非公平锁,他无法保证等待的线程获取锁的顺序。对于ReentrantLock默认情况下是非公平锁,但是可以设置为公平锁。

4. 读写锁

读写锁对一个资源(如文件)的访问分成了2个锁,一个读锁和一个写锁。

如果一个线程已经申请了读锁,则其他的线程也可以申请读锁,但申请写锁会被阻塞,直到读锁被释放;如果一个线程已经申请了写锁,则其他线程申请读锁或写锁都会被阻塞,直到该写锁被释放。就是说读锁可以和读锁共存,而写锁和任何锁都不能共存。

synchronized的缺陷

synchronized是Java中的一个关键字。

我们知道如果一段代码被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块的时候,其他的线程便只能一直等待,等待获取锁的线程释放锁,而这里获锁的线程释放锁有两种情况:

1. 获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

2. 线程执行发生异常,此时JVM会让线程自动释放锁。

如果这个获取锁的线程由于I/O或者其他原因(如调用sleep方法)被阻塞了,但是有没有释放锁,其他线程便只能干巴巴地等待,这会很影响执行效率。

因此就需要一种机制可以不让等待的线程一直无限期地等待下去(如只等待一定的时间或者能够响应中断),通过Lock就可以办到。

再如:当有多个线程读取文件时,读操作和写操作会发生冲突,写操作和写操作会发生冲突,但是读操作和读操作不会发生冲突。

如果此时采用synchronized关键字就会出现一个问题:如果多个线程只是进行读操作,所以当一个线程进行读操作时,其他的线程只能等待无法进行读操作。

因此就需要 一种机制使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。

另外,通过Lock可以知道线程有没有成功获取锁,这是synchronized无法办到的。

注意:

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

2. 使用synchronized不需要用户手动去释放锁,当synchronized方法或代码块执行完后,系统会自动让线程释放对锁的占用;而Lock则必须要用户手动释放锁,如果没有主动释放,就有可能导致出现死锁。

java.util.concurrent.locks包下的常用的类

public interface Lock {

	/*
	 * 获取锁,如果锁被其他线程获取,则等待
	 */
    void lock();

    /*
     * 通过这个方法获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,
     * 即中断线程的等待状态,也就是说,当两个线程同时通过lock.lockInterruptibly()
     * 获取某个锁时,假如此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()
     * 方法能够中断线程B的等待过程。
     * 当一个线程获取锁以后,是不会被interruptibly方法中断的,只能中断阻塞过程中的线程
     */
    void lockInterruptibly() throws InterruptedException;

    /*
     * 用来尝试获取锁,成功返回true,失败返回false
     * 这个方法无论结果如何都会立即返回,在拿不到锁时不会一直在那等待
     */
    boolean tryLock();

    
    /*
     * 这个方法再拿不到锁时会等待一段时间,在时间期限之内如果还能不到锁
     * 就返回false,如果在刚开始拿到锁或者在等待时间内拿到锁,则返回true
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

   /*
    * 释放锁
    */
    void unlock();

    
    Condition newCondition();
}

ReentrantLock类继承了Lock接口。



public interface ReadWriteLock {
    
	/*
	 * 获取读锁
	 */
    Lock readLock();

    /*
     * 获取写锁
     */
    Lock writeLock();
}

ReentrantReadWriteLock继承了ReadWriteLock接口。



而ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock为ReentrantReadWriteLock的内部类。他们都实现了ReadWriteLock接口。

public static void main(String[] args){  
 
    	/*
    	 * 获取读锁
    	 */
    	Lock readLock = new ReentrantReadWriteLock(true).readLock();//公平锁
    	
    	/*
    	 * 
    	 * 获取写锁
    	 */
    	Lock writeLock = new ReentrantReadWriteLock().writeLock();
    	
    	
    	
    }


Lock和synchronized的比较

1) Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现。

2) synchronized发生异常时,会自动释放线程占有的锁;而Lock在发生异常时,如果没有主动通过unLock()方法去释放锁,则可能会造成死锁,因此使用Lock时需要在finally块中释放锁。

3) Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。

4) 通过Lock可以知道有没有成功获取锁,而synchronized无法办到。

5) Lock可以设置公平锁(构造方法中加true),默认为非公平锁;而synchronized为非公平锁。

6) Lock可以提高多个线程进行读操作的效率(读锁)。

Lock适合读多写少的操作。

参考:https://www.cnblogs.com/dolphin0520/p/3923167.html



版权声明:本文为qq_32998153原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。