1 简介

  ReentrantLock和ReentrantReadWriteLock都是可重入锁。可重入锁,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁

  ReentrantLock和ReentrantReadWriteLock都支持获取锁时的公平和非公平性选择。默认是非公平的

  ReentrantLock读读、读写、写写全部互斥。ReentrantReadWriteLock读读共享,读写互斥,写写互斥,且支持锁降级

  ReentrantReadWriteLock由于读读共享,且支持锁降级,所及效率会高一些。由于它读写不共享,所以在读写高并发操作时,可能导致写的操作锁饥饿。

  是否可重入 公平性选择 读读 读写 写写 锁降级
ReentrantLock 互斥 互斥 互斥 不支持
ReentrantReadWriteLock 共享 互斥 互斥 支持

 

 

 



2 ReentrantLock示例

public class ReentrantLockTest1 {

    static ReentrantLock lo = new ReentrantLock();

    //读读互斥  读写互斥 写写互斥
    public static void main(String[] args) {
        
        for (int i = 0;i < 5;i++) {
            new Thread(() -> operate(), "要进行读操作的线程" + i).start();
        }

        for (int i = 0;i < 5;i++) {
            new Thread(() -> operate(), "要进行写操作的线程线程" + i).start();
        }
    }

    private static void operate() {
        lo.lock();
        System.out.println(Thread.currentThread().getName() + "开始操作-----------");
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "结束操作-----------");
        lo.unlock();
    }
}

执行结果,只有执行完一个操作,才能够执行另一个操作,所有操作互斥

要进行读操作的线程0开始操作-----------
要进行读操作的线程0结束操作-----------
要进行读操作的线程1开始操作-----------
要进行读操作的线程1结束操作-----------
要进行读操作的线程2开始操作-----------
要进行读操作的线程2结束操作-----------
要进行读操作的线程3开始操作-----------
要进行读操作的线程3结束操作-----------
要进行读操作的线程4开始操作-----------
要进行读操作的线程4结束操作-----------
要进行写操作的线程线程0开始操作-----------
要进行写操作的线程线程0结束操作-----------
要进行写操作的线程线程1开始操作-----------
要进行写操作的线程线程1结束操作-----------
要进行写操作的线程线程2开始操作-----------
要进行写操作的线程线程2结束操作-----------
要进行写操作的线程线程3开始操作-----------
要进行写操作的线程线程3结束操作-----------
要进行写操作的线程线程4开始操作-----------
要进行写操作的线程线程4结束操作-----------

Process finished with exit code 0

 

 

3 ReentrantReadWriteLock 示例

  ReentrantReadWriteLock分为:

    读锁-ReentrantReadWriteLock.ReadLock

    写锁-ReentrantReadWriteLock.WriteLock

 

3.1 示例1

  下面示例演示出了:读读不互斥,读写互斥,写写互斥

public class ReentrantLockTest2 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
    
    public static void main(String[] args) {


        for (int i = 0;i < 10;i++) {
            new Thread(() -> read(), "read线程" + i).start();
        }
        
        for (int i = 0;i < 10;i++) {
            new Thread(() -> write(), "write线程" + i).start();
        }
    }

    private static void read() {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "开始读-----------");
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "结束读-----------");
        readLock.unlock();
    }

    private static void write() {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "开始写-----------");
        try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "结束写-----------");
        writeLock.unlock();
    }
}

 

执行结果,发现多个读操作可以同时进行,读写操作互斥,写写也互斥

read线程0开始读-----------
read线程4开始读-----------
read线程3开始读-----------
read线程1开始读-----------
read线程2开始读-----------
read线程0结束读-----------
read线程3结束读-----------
read线程4结束读-----------
read线程1结束读-----------
read线程2结束读-----------
write线程0开始写-----------
write线程0结束写-----------
write线程1开始写-----------
write线程1结束写-----------
write线程2开始写-----------
write线程2结束写-----------
write线程3开始写-----------
write线程3结束写-----------
write线程4开始写-----------
write线程4结束写-----------

Process finished with exit code 0

 

 

3.2 示例2

  这个示例是为了进一步演示读写互斥,和示例2相比,这里for循环先调用写,再调用的读,发现读写,还是互斥

public class ReentrantLockTest3 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();

    public static void main(String[] args) {

        for (int i = 0;i < 5;i++) {
            new Thread(() -> write(), "write线程" + i).start();
        }

        for (int i = 0;i < 5;i++) {
            new Thread(() -> read(), "read线程" + i).start();
        }
    }

    private static void read() {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "开始读-----------");
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "结束读-----------");
        readLock.unlock();
    }

    private static void write() {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "开始写-----------");
        try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "结束写-----------");
        writeLock.unlock();
    }
}

 

执行结果

write线程0开始写-----------
write线程0结束写-----------
write线程1开始写-----------
write线程1结束写-----------
write线程2开始写-----------
write线程2结束写-----------
write线程3开始写-----------
write线程3结束写-----------
write线程4开始写-----------
write线程4结束写-----------
read线程0开始读-----------
read线程1开始读-----------
read线程2开始读-----------
read线程4开始读-----------
read线程3开始读-----------
read线程4结束读-----------
read线程1结束读-----------
read线程2结束读-----------
read线程0结束读-----------
read线程3结束读-----------

Process finished with exit code 0

 

 

4 ReentrantReadWriteLock锁降级

  简单来说,就是一个线程在持有写锁,且还未释放的时候,可以去获取读锁,这样子,该线程就可以同时持有读写锁。

  多个线程操作一个变量a,使用ReentrantReadWriteLock线程1对a进行修改,值为100,它想要保证100这个值被其它所有的线程获取到,该怎么做?

  那么我们要去写的时候,先去获取写锁(此时其它线程不能读写),写完了,再获取读锁(此时其它线程不能读写),此时同时持有读写锁,然后释放写锁,只持有读锁((此时其它线程不能写,但是可以读)),这个从写锁变为读锁的过程,就叫做锁降级。在持有读锁变为持有写锁的过程中,其它线程都不能写,保证我写的数据能够被其它线程看到。如果不能同时持有读写锁那么就只能这么操作,获取写锁-写-释放写锁-获取读锁-其它线程读-释放读锁,在释放写锁和获取读锁之间就会存在空隙,有可能被其它线程进行写操作,导致它写的结果不能被其他线程获取。

   所以,锁降级解决的就是即写即读的问题

 

5 锁降级示例

5.1 示例1

  在释放写锁后,释放读锁前,其它线程可读,且中间其它线程都不可写

public class ReentrantLockTest5 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();

    public static void main(String[] args) {

        for (int i = 0;i < 10;i++) {
            new Thread(() -> write(), "write线程" + i).start();
        }

        for (int i = 0;i < 10;i++) {
            new Thread(() -> read(), "read线程" + i).start();
        }

    }

    private static void read() {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "开始读-----------");
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "结束读-----------");
        readLock.unlock();
    }

    private static void write() {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "开始写-----------");
        try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "结束写结束写-----------");

        readLock.lock();  //锁降级 同时持有写锁和读锁
        System.out.println(Thread.currentThread().getName() + "锁降级-----------");
        System.out.println(Thread.currentThread().getName() + "释放写锁-----------");
        writeLock.unlock(); //释放写锁,只持有读锁,此时其它线程可读
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //此时,它只拥有读锁
        System.out.println(Thread.currentThread().getName() + "释放读锁-----------");
        readLock.unlock();  //
    }
}

 

执行结果,可以看到write8在释放了写锁后释放读锁前,其它线程进来读了

write线程1开始写-----------
write线程1结束写结束写-----------
write线程1锁降级-----------
write线程1释放写锁-----------
write线程1释放读锁-----------
write线程0开始写-----------
write线程0结束写结束写-----------
write线程0锁降级-----------
write线程0释放写锁-----------
write线程0释放读锁-----------
write线程2开始写-----------
write线程2结束写结束写-----------
write线程2锁降级-----------
write线程2释放写锁-----------
write线程2释放读锁-----------
write线程3开始写-----------
write线程3结束写结束写-----------
write线程3锁降级-----------
write线程3释放写锁-----------
write线程3释放读锁-----------
write线程4开始写-----------
write线程4结束写结束写-----------
write线程4锁降级-----------
write线程4释放写锁-----------
write线程4释放读锁-----------
write线程5开始写-----------
write线程5结束写结束写-----------
write线程5锁降级-----------
write线程5释放写锁-----------
write线程5释放读锁-----------
write线程7开始写-----------
write线程7结束写结束写-----------
write线程7锁降级-----------
write线程7释放写锁-----------
write线程7释放读锁-----------
write线程6开始写-----------
write线程6结束写结束写-----------
write线程6锁降级-----------
write线程6释放写锁-----------
read线程1开始读-----------
read线程1结束读-----------
write线程6释放读锁-----------
write线程9开始写-----------
write线程9结束写结束写-----------
write线程9锁降级-----------
write线程9释放写锁-----------
read线程0开始读-----------
read线程0结束读-----------
write线程9释放读锁-----------
write线程8开始写-----------
write线程8结束写结束写-----------
write线程8锁降级-----------
write线程8释放写锁-----------
read线程7开始读-----------
read线程3开始读-----------
read线程5开始读-----------
read线程4开始读-----------
read线程8开始读-----------
read线程9开始读-----------
read线程2开始读-----------
read线程6开始读-----------
read线程9结束读-----------
read线程7结束读-----------
read线程3结束读-----------
read线程4结束读-----------
read线程6结束读-----------
read线程2结束读-----------
read线程8结束读-----------
read线程5结束读-----------
write线程8释放读锁-----------

Process finished with exit code 0

 

5.2 示例2

  在释放读锁前其它线程不可写

 

 

public class ReentrantLockTest4 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();


    public static void main(String[] args) {


        for (int i = 0;i < 10;i++) {
            new Thread(() -> write(), "write线程" + i).start();
        }

        for (int i = 0;i < 10;i++) {
            new Thread(() -> read(), "read线程" + i).start();
        }

    }

    private static void read() {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "开始读-----------");
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "结束读-----------");
        readLock.unlock();
    }

    private static void write() {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "开始写-----------");
        try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "结束写结束写-----------");

        readLock.lock();  //锁降级  同时持有写锁和读锁
        System.out.println(Thread.currentThread().getName() + "锁降级-----------");
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "释放写锁-----------");
        writeLock.unlock(); //释放写锁-此时值持有读锁
        System.out.println(Thread.currentThread().getName() + "释放读锁-----------");
        readLock.unlock();  //释放读锁
    }
}

执行结果

write线程1开始写-----------
write线程1结束写结束写-----------
write线程1锁降级-----------
write线程1释放写锁-----------
write线程1释放读锁-----------
write线程2开始写-----------
write线程2结束写结束写-----------
write线程2锁降级-----------
write线程2释放写锁-----------
write线程2释放读锁-----------
write线程0开始写-----------
write线程0结束写结束写-----------
write线程0锁降级-----------
write线程0释放写锁-----------
write线程0释放读锁-----------
write线程3开始写-----------
write线程3结束写结束写-----------
write线程3锁降级-----------
write线程3释放写锁-----------
write线程3释放读锁-----------
write线程4开始写-----------
write线程4结束写结束写-----------
write线程4锁降级-----------
write线程4释放写锁-----------
write线程4释放读锁-----------
write线程7开始写-----------
write线程7结束写结束写-----------
write线程7锁降级-----------
write线程7释放写锁-----------
write线程7释放读锁-----------
write线程8开始写-----------
write线程8结束写结束写-----------
write线程8锁降级-----------
write线程8释放写锁-----------
write线程8释放读锁-----------
write线程5开始写-----------
write线程5结束写结束写-----------
write线程5锁降级-----------
write线程5释放写锁-----------
write线程5释放读锁-----------
write线程6开始写-----------
write线程6结束写结束写-----------
write线程6锁降级-----------
write线程6释放写锁-----------
write线程6释放读锁-----------
write线程9开始写-----------
write线程9结束写结束写-----------
write线程9锁降级-----------
write线程9释放写锁-----------
write线程9释放读锁-----------
read线程0开始读-----------
read线程1开始读-----------
read线程2开始读-----------
read线程3开始读-----------
read线程4开始读-----------
read线程5开始读-----------
read线程6开始读-----------
read线程7开始读-----------
read线程8开始读-----------
read线程9开始读-----------
read线程4结束读-----------
read线程1结束读-----------
read线程0结束读-----------
read线程2结束读-----------
read线程5结束读-----------
read线程3结束读-----------
read线程6结束读-----------
read线程9结束读-----------
read线程7结束读-----------
read线程8结束读-----------

 

5.3 再来个更清晰的例子

  持有读写锁,释放写锁后释放读锁前,其它线程可读

public class ReentrantLockTest8 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();


    //读读共享  读写互斥 写写互斥
    public static void main(String[] args) {

        CountDownLatch c = new CountDownLatch(10);

        new Thread(() ->write(c), "write线程" ).start();


        for (int i = 0;i < 10;i++) {
            new Thread(() -> read(c), "read线程" + i).start();
        }

    }

    private static void read(CountDownLatch c) {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "开始读-----------");
        System.out.println(Thread.currentThread().getName() + "结束读-----------");
        readLock.unlock();
        c.countDown();
    }

    private static void write(CountDownLatch c) {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "写-----------");
        readLock.lock();  //锁降级 同时持有写锁和读锁


        System.out.println(Thread.currentThread().getName() + "释放写锁-----------");
        writeLock.unlock(); //释放写锁,只持有读锁,此时其它线程可读

        try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(Thread.currentThread().getName() + "释放读锁-----------");
        readLock.unlock();  //释放读锁
    }
}

执行结果,执行完成

write线程写-----------
write线程释放写锁-----------
read线程0开始读-----------
read线程5开始读-----------
read线程4开始读-----------
read线程4结束读-----------
read线程1开始读-----------
read线程1结束读-----------
read线程7开始读-----------
read线程7结束读-----------
read线程6开始读-----------
read线程5结束读-----------
read线程3开始读-----------
read线程9开始读-----------
read线程9结束读-----------
read线程2开始读-----------
read线程0结束读-----------
read线程2结束读-----------
read线程3结束读-----------
read线程6结束读-----------
read线程8开始读-----------
read线程8结束读-----------
write线程释放读锁-----------

Process finished with exit code 0

持有读写锁,释放读锁和写锁前,其它线程不可读

try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); }放到释放写锁前

public class ReentrantLockTest7 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();


    //读读共享  读写互斥 写写互斥
    public static void main(String[] args) {

        CountDownLatch c = new CountDownLatch(10);

        new Thread(() ->write(c), "write线程" ).start();


        for (int i = 0;i < 10;i++) {
            new Thread(() -> read(c), "read线程" + i).start();
        }

    }

    private static void read(CountDownLatch c) {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "开始读-----------");
        System.out.println(Thread.currentThread().getName() + "结束读-----------");
        readLock.unlock();
        c.countDown();
    }

    private static void write(CountDownLatch c) {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "写-----------");
        readLock.lock();  //锁降级 同时持有写锁和读锁

        try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(Thread.currentThread().getName() + "释放写锁-----------");
        writeLock.unlock(); //释放写锁,只持有读锁,此时其它线程可读

        System.out.println(Thread.currentThread().getName() + "释放读锁-----------");
        readLock.unlock();  //释放读锁
        
    }
}

执行结果,卡住了,没有执行完