使用ReentrantReadWriteLock类

一、使用ReentrantReadWriteLock类

类ReentrantLock虽然具有完全互斥排他的效果,即同一时间只有一个线程在执行 lock() 方法后面的任务,虽然可以保证线程的安全性,但是效率却十分低下,所以还有一种读写锁 ReentrantReadWriteLock 类,可以用它来加快运行的效率

读写锁共有两个锁,一个是读相关的锁,也称作共享锁,另一个是与写操作相关的锁,也称作排他锁。同时规定如下:

  1. 读锁与读锁之间不互斥
  2. 写锁与写锁之间互斥
  3. 读锁与写锁之间互斥

进行读取操作的多个 Thread 可以同时获取读锁,进行读取,而进行写入操作的 Thread 只能在获取写锁后才能进行写入操作,即多个 Thread 可以同时进行读取操作,但是同一时刻只允许一个 Thread 进行写入操作

二、读读共享

读锁与读锁之间不互斥

class MyService {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " 获得读锁 "
                    + System.currentTimeMillis());
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

}

class ThreadA extends Thread {

    private MyService myService;

    public ThreadA(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.read();
    }

}

class ThreadB extends Thread {

    private MyService myService;

    public ThreadB(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.read();
    }

}

public class ReadAndRead {

    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA threadA = new ThreadA(service);
        threadA.setName("AAAA");
        threadA.start();

        Thread.sleep(2000);

        ThreadB threadB = new ThreadB(service);
        threadB.setName("BBBB");
        threadB.start();
    }

}

结果是:

BBBB 获得读锁 1541404600765
AAAA 获得读锁 1541404600765

从时间可以看到,线程 AAAA 和线程 BBBB 几乎是同时进入 lock() 方法的,说明进行读取操作的多个线程可以同时获取读锁,进行读取,即读锁与读锁之间不互斥

此时 lock.readLock() 实际是返回 ReentrantReadWriteLock 类的内部类 ReadLock,即读取锁,该内部类实现了 Lock 接口, ReadLock 内部类的 lock() 方法用于将该读取锁进行锁定

三、写写互斥

写锁与写锁之间互斥

class MyService2 {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void write() {
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + " 获得写锁 "
                    + System.currentTimeMillis());
            Thread.sleep(10000);
            System.out.println(Thread.currentThread().getName() + " 结束写锁 "
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

}

class ThreadA2 extends Thread {

    private MyService2 service2;

    public ThreadA2(MyService2 service2) {
        this.service2 = service2;
    }

    @Override
    public void run() {
        service2.write();
    }

}

class ThreadB2 extends Thread {

    private MyService2 service2;

    public ThreadB2(MyService2 service2) {
        this.service2 = service2;
    }

    @Override
    public void run() {
        service2.write();
    }
}

public class WriteAndWrite {

    public static void main(String[] args) {
        MyService2 service2 = new MyService2();
        ThreadA2 threadA2 = new ThreadA2(service2);
        threadA2.setName("AAA");
        ThreadB2 threadB2 = new ThreadB2(service2);
        threadB2.setName("BBB");

        threadA2.start();
        threadB2.start();
    }

}

结果是:

AAA 获得写锁 1541405542280
AAA 结束写锁 1541405552281
BBB 获得写锁 1541405552282
BBB 结束写锁 1541405562282

可以看到,在线程 AAA 获得写锁的时候,线程 BBB 并没有获得,过了 10s 之后,等到线程 AAA 执行完了写入操作后,线程 BBB 才可以获得写锁并执行写入操作。说明当多个线程同时执行写入操作时,后执行的线程只能等待前一个线程执行完写入操作,释放写锁,然后该线程获取写锁之后才能执行写入操作

语句 lock.writeLock() 返回的是 ReentrantReadWriteLock 类的内部类 WriteLock,该内部类实现了 Lock 接口,重写了 lock() 方法,用于将写锁进行锁定

四、读写操作

读锁和写锁之间是互斥的

class MyService3 {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " 获得读锁 "
                    + System.currentTimeMillis());
            Thread.sleep(10000);
            System.out.println(Thread.currentThread().getName() + " 结束读锁 "
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

    public void write() {
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + " 获得写锁 "
                    + System.currentTimeMillis());
            Thread.sleep(10000);
            System.out.println(Thread.currentThread().getName() + " 结束写锁 "
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

}

class ThreadA3 extends Thread {

    private MyService3 myService3;

    public ThreadA3(MyService3 myService3) {
        this.myService3 = myService3;
    }

    @Override
    public void run() {
        myService3.read();
    }

}

class ThreadB3 extends Thread {

    private MyService3 myService3;

    public ThreadB3(MyService3 myService3) {
        this.myService3 = myService3;
    }

    @Override
    public void run() {
        myService3.write();
    }
}

public class ReadAndWrite {

    public static void main(String[] args) {
        MyService3 service3 = new MyService3();
        ThreadA3 threadA3 = new ThreadA3(service3);
        threadA3.setName("AAA");
        ThreadB3 threadB3 = new ThreadB3(service3);
        threadB3.setName("BBB");

        threadA3.start();
        threadB3.start();
    }

}

结果是:

AAA 获得读锁 1541407541955
AAA 结束读锁 1541407551955
BBB 获得写锁 1541407551955
BBB 结束写锁 1541407561955

从结果可以看到,线程 AAA 先获取读锁,进行读取操作,线程 BBB 只有在线程 AAA 执行完写入操作后,才能获取写锁,进行写入,说明读取和写入是互斥的。如果修改代码,让线程 AAA 先执行写入,然后线程 BBB 在执行读取,结果也是互斥的

结合写写和读写操作,可以总结出,只要有一个线程执行写入操作,其他线程无论执行哪种操作都是互斥的

五、synchronized和ReentrantLock的对比

我直接引用这位博主的总结了,写得很好 https://www.cnblogs.com/xrq730/p/4855631.html

六、参考

《Java多线程变成核心技术》
https://www.cnblogs.com/xrq730/p/4855631.html

猜你喜欢

转载自blog.csdn.net/babycan5/article/details/83989637