一、使用ReentrantReadWriteLock类
类ReentrantLock虽然具有完全互斥排他的效果,即同一时间只有一个线程在执行 lock() 方法后面的任务,虽然可以保证线程的安全性,但是效率却十分低下,所以还有一种读写锁 ReentrantReadWriteLock 类,可以用它来加快运行的效率
读写锁共有两个锁,一个是读相关的锁,也称作共享锁,另一个是与写操作相关的锁,也称作排他锁。同时规定如下:
- 读锁与读锁之间不互斥
- 写锁与写锁之间互斥
- 读锁与写锁之间互斥
进行读取操作的多个 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