上一篇文章提到通过synchronized实现同步,而在这篇文章中,同步会通过Lock和Condition配合实现,两者都来自jdk1.5并发包
Lock
本文章会使用java.util.concurrent.locks包内的ReentrantLock类(唯一实现Lock接口的类)和Condition接口模拟出synchronized和wait()配合的同步实现,首先介绍Lock类的方法
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
1. lock():用来获取锁,如果锁已被其他线程获取,则进行等待。也就是上锁
2. unlock():释放锁,也就是开锁,与lock()合用
Lock lock = ...;
try{
//获取锁
lock.lock();
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
注:获取锁需要放在try内,为了避免可能会发生的死锁,finally里都必须要释放锁
3. trylock():方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待
Lock lock = ...;
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
4. tryLock(long time, TimeUnit unit):和tryLock()方法类似,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true
5. lockInterruptibly():这个方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
try {
lock.lockInterruptibly();
//.....
}
finally {
lock.unlock();
}
注:当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为本身在前面的文章中讲过单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去
ReentrantLock
ReentrantLock,意思是“可重入锁”。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法
ReadWriteLock
ReadWriteLock也是一个接口,在它里面只定义了两个方法:
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading.
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing.
*/
Lock writeLock();
}
一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。下面的ReentrantReadWriteLock实现了ReadWriteLock接口
ReentrantReadWriteLock
ReentrantReadWriteLock里面提供的最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。列举一个读写锁的实例:
public class ReadWriteLock {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
// 创建读写可重用锁
ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
new Thread() {
public void run() {
try {
// 对读锁获取锁
rwlock.readLock().lock();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "正在进行读操作");
}
System.out.println(Thread.currentThread().getName() + "读取完毕");
} catch (Exception e) {
// TODO: handle exception
} finally {
// 读锁释放锁
rwlock.readLock().unlock();
}
};
}.start();
new Thread() {
public void run() {
try {
// 对读锁获取锁
rwlock.readLock().lock();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "正在进行读操作");
}
System.out.println(Thread.currentThread().getName() + "读取完毕");
} catch (Exception e) {
// TODO: handle exception
} finally {
// 读锁释放锁
rwlock.readLock().unlock();
}
};
}.start();
}
}
结果:
可以发现两个线程在读锁被获取的情况下做读入操作时是并行的,可以提高读写效率
不过要注意的是,如果有一个线程已经占用了读锁,其他线程可以并行读取,而如果要申请写锁,则申请写锁的线程会一直等待释放读锁。但是,如果有一个线程已经占用了写锁,此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁
Condition
synchronized能和wait()、notify()、notifyAll()配合使用,Lock同样也有对应的等待和唤醒方法,这些方法的实现接口就是Condition。由于这篇主要讲Lock,就不对Condition多做讲解,在此展示一下Condition的API文档
await():对应wait()方法
signal():对应notify()方法
signalAll():对应notifyAll()方法
注:Condition对象创建方法为 lock.newCondition(), lock为ReentrantLock的实例化对象
Lock+Condition实现同步
在 Java多线程(三):线程安全问题 (上)synchronized 中使用synchronized与wait配合实现了同步,下面我用代码演示一下Lock的同步实现,注意Lock锁的实现,要新建的是ReentrantLock对象
public class LockDemo {
public static void main(String[] args) {
Res1 res = new Res1();
InpThread1 inpThread = new InpThread1(res);
OutThread1 outThread = new OutThread1(res);
inpThread.start();
outThread.start();
}
}
class Res1 {
public String name;
public String sex;
// flag false out线程打印
// flag true inp线程读取
public boolean flag = false;
public Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
}
class InpThread1 extends Thread {
public Res1 res;
public InpThread1(Res1 res) {
this.res = res;
}
@Override
public void run() {
int count = 0;
while (true) {
// if(res.lock.tryLock()) 判断trylock更保险,如果可以锁,自动会执行lock()
try {
// 获取锁
res.lock.lock();
// 如果是只读操作,进入休眠状态
if (res.flag) {
try {
res.condition.await();
} catch (InterruptedException e) {
}
}
if (count == 0) {
res.name = "ymk";
res.sex = "男";
} else {
res.name = "zyy";
res.sex = "女";
}
count = (count + 1) % 2;
res.flag = true;
res.condition.signal();
} catch (Exception e) {
// TODO: handle exception
} finally {
// 释放锁
res.lock.unlock(); // 防止出现意外,锁永远不会得到释放
}
}
}
}
class OutThread1 extends Thread {
public Res1 res;
public OutThread1(Res1 res) {
this.res = res;
}
@Override
public void run() {
while (true) {
try {
// 上锁
res.lock.lock();
if (!res.flag) {
res.condition.await();
}
System.out.println(res.name + "------" + res.sex);
res.flag = false;
res.condition.signal();
} catch (Exception e) {
// TODO: handle exception
} finally {
// 释放锁
res.lock.unlock();
}
}
}
}
结果与synchronized实现相同:
Lock和synchronized的异同
共同点:
1. 都能解决线程安全问题
2. 都是可重入锁(基于线程的分配,而不是基于方法调用的分配)
区别:
1. synchronized和wait()配合使用,Lock与Condition的await方法配合
2. synchronized是Java内置实现的锁,不可手动开锁、解锁;而Lock是个接口,可以实现手动开锁、解锁
3. synchronized是不可中断了,而Lock的lockInterruptibly()具有可中断性
4. synchronized是非公平锁,Lock可以设置是否公平(公平锁:尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所)
文章内容后续可能会进行补充...
笔者水平有限,若有错误欢迎纠正,希望多获得大家的建议
参考:https://www.cnblogs.com/dolphin0520/p/3923167.html
https://www.jianshu.com/p/be2dc7c878dc
https://blog.youkuaiyun.com/qq_40409115/article/details/80229188