ReentrantLock和ReentrantReadWriteLock对比
作者:追梦1819
原文:https://blog.csdn.net/weixin_39759846/article/details/90239241
版权声明:本文为博主原创文章,转载请附上博文链接!
ReentrantLock
一、简介
ReentrantLock重入锁和synchronize关键字一样,是互斥锁。比synchronize关键字更加灵活。
二、基本方法
三、与synchronize对比
-
demo演示
线程不安全:
public class WriteAndReadThread { public static void main(String[] args) { Person person = new Person(); Thread t1 = new Output(person); Thread t2 = new Input(person); t1.start(); t2.start(); } } // 写数据的线程 class Output extends Thread { private Person person; public Output(Person person) { this.person = person; } @Override public void run() { int count = 0; while (true) { if (count == 0) { person.name = "小明"; person.gender = "男"; } else { person.name = "小红"; person.gender = "女"; } count = (count + 1) % 2; // 奇数偶数轮流展现 person.flag = true; } } } // 读数据的线程 class Input extends Thread { private Person person; public Input(Person person) { this.person = person; } @Override public void run() { while (true) { System.out.println(person.name + "," + person.gender); } } } class Person { public String name; public String gender; public boolean flag = false; }
以上代码没有考虑线程安全问题,读写线程会竞争共享资源,导致数据紊乱。
1)线程安全(加synchronize关键字):
public class ThreadTwo { public static void main(String[] args) { Person person = new Person(); Thread t1 = new Output(person); Thread t2 = new Input(person); t1.start(); t2.start(); } } // 写的线程 class Output extends Thread{ private Person person; public Output(Person person){ this.person=person; } @Override public void run(){ int count = 0; while (true){ synchronized (person){ if(person.flag){ try { person.wait(); // 唤醒等待的线程 } catch (InterruptedException e) { e.printStackTrace(); } } if(count==0){ person.name="小明"; person.gender="男"; }else { person.name="小红"; person.gender="女"; } count=(count+1)%2; person.flag=true; person.notify(); } } } } // 读数据的线程 class Input extends Thread{ private Person person; public Input(Person person){ this.person=person; } @Override public void run(){ while (true){ synchronized (person){ if(!person.flag){ try { person.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(person.name+","+person.gender); person.flag=false; person.notify(); } } } } class Person{ public String name; public String gender; public boolean flag=false; }
2)线程安全(用Lock):
public class LockDemo { public static void main(String[] args) { Person2 person2 = new Person2(); Condition condition = person2.lock.newCondition(); Input2 input2 = new Input2(person2,condition); Output2 output2 = new Output2(person2,condition); input2.start(); output2.start(); } } // 写的线程 class Output2 extends Thread { private Person2 person; private Condition condition; public Output2(Person2 person, Condition condition) { this.person = person; this.condition = condition; } @Override public void run() { int count = 0; while (true) { try { person.lock.lock(); if (person.flag) { try { condition.await(); // 使线程休眠,作用等于synchronize中的thread.wait()方法 } catch (InterruptedException e) { e.printStackTrace(); } } if (count == 0) { person.name = "小明"; person.gender = "男"; } else { person.name = "小红"; person.gender = "女"; } count = (count + 1) % 2; person.flag = true; condition.signal(); }catch (Exception e){ }finally { person.lock.unlock(); // 必须手动关闭线程 } } } } // 读的线程 class Input2 extends Thread { private Person2 person; private Condition condition; // 该接口的作用是精确控制锁的行为 public Input2(Person2 person, Condition condition) { this.person = person; this.condition = condition; } @Override public void run() { while (true) { try { person.lock.lock(); if (!person.flag) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(person.name + "," + person.gender); person.flag = false; condition.signal();//唤醒线程 }catch (Exception e){ }finally { person.lock.unlock(); } } } } class Person2 { public String name; public String gender; public boolean flag = false; // 该属性是用来控制线程之间的通讯 Lock lock = new ReentrantLock(); }
-
总结
相同点:
都是用于线程同步锁,都是互斥锁。
不同点:
1.如果用汽车来类比,synchronize相当于自动挡,Lock相当于手动挡。即:synchronize是内置锁,只要加上synchronize的代码的地方开始,代码结束的地方自动释放资源。lock必须手动加锁,手动释放资源。
2.synchronize优点是代码量少,自动化。缺点是扩展性低,不够灵活。
3.Lock优点是扩展性好,灵活。缺点是代码量相对稍多。
4.释放锁的情况:
synchronize:1)线程执行完毕;2)线程发生异常;3)线程进入休眠状态。
Lock:通过unLock()方法。
注意点:
1.wait()和notify()/notifyAll()必须出现在synchronize修饰的代码块中;
2.资源的释放通常放在finally中;
3.最好不要将lock()方法写在try{}中,因为如果 发生异常的话,抛出异常,同时锁资源无法释放。
ReentrantReadWriteLock
一、简介
ReentrantLock虽然可以灵活地实现线程安全,但是他是一种完全互斥锁,即某一时刻永远只允许一个线程访问共享资源,不管是读数据的线程还是写数据的线程。这导致的结果就是,效率低下。
ReentrantReadWriteLock类的出现很好的解决了该问题。该类实现了ReadWriteLock接口,而ReadWriteLock接口中维护了两个锁:读锁(共享锁)和写锁(排他锁)。
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
二、基本方法
三、使用
ReentrantReadWriteLock中维护了读锁和写锁。允许线程同时读取共享资源;但是如果有一个线程是写数据,那么其他线程就不能去读写该资源。即会出现三种情况:读读共享,写写互斥,读写互斥。
以下以代码演示:
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
Lock readLock = reentrantReadWriteLock.readLock();
Lock writeLock = reentrantReadWriteLock.readLock();
new Thread(new Runnable() {
@Override
public void run() {
// writeLock.lock(); // 写锁加锁
readLock.lock(); // 读锁加锁
try {
for (int i = 0; i < 10; i++) {
System.out.println("我是第一个线程,线程名是"+Thread.currentThread().getName()+",当前时间是"+System.currentTimeMillis());
}
Thread.sleep(2000); // 休眠2s
}catch (Exception e){
}finally {
// writeLock.unlock();// 写锁释放锁
readLock.unlock();// 读锁释放锁
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// writeLock.lock(); // 写锁加锁
readLock.lock();// 读锁加锁
try {
for (int i = 0; i < 10; i++) {
System.out.println("我是第二个线程,线程名是"+Thread.currentThread().getName()+",当前时间是"+System.currentTimeMillis());
}
}catch (Exception e){
}finally {
// writeLock.unlock();// 写锁释放锁
readLock.unlock();
}
}
}).start();
以上两个线程如果是读锁,则会同时执行(打印的时间几乎相等),但是如果是写锁,则不会同时执行(打印时间相差2s)。