使用Lock重构生产者消费者及线程通信
在上一篇在文章中,都是使用synchronized
对方法进行加锁,然后通过wait()
、notify()
、notifyAll
进行线程通信。除此之外,还可以使用Lock
给方法加锁,然后使用Condition
接口提供的await()
、singalAll()
进行线程通信。二者的对应关系如下表:
Lock加锁目的 | synchronzied | Lock接口的Lock()、unlock()方法 |
---|---|---|
使得当前线程处于等待状态 | wait() | Condition接口提供的await() |
唤醒当前对象的线程 | notify()、notifyAll() | Condition接口提供的singal()、signalAll()方法 |
接下来使用Lock
和Condition
重构之前的第一个生产者和消费者代码。
package com.geovis.bin.custom.study.wangbin.lock.contion.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: Wangb
* @EMail: 1149984363@qq.com
* @Date: 24/12/2021 下午4:53
* @Description
*/
public class CarStock1 {
int cars;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//生产车
public void productCar() {
lock.lock();
try {
if (cars <20) {
System.out.println("生产车...."+ cars);
cars++;
condition.signalAll();//唤醒
} else {
condition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//消费车
public void resumeCar() {
lock.lock();
try {
if (cars >0) {
System.out.println("销售车...."+ cars);
cars--;
condition.signalAll();//唤醒
} else {
condition.await();//等待
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
剩下的消费者、生产者、实体类、测试类都与上一篇文章一样。
循环打印123案例
为了加深对lock
的理解,我们再来学习一个循环打印123的例子。
package com.geovis.bin.custom.study.wangbin.lock.contion.print;
/**
* @Author: Wangb
* @EMail: 1149984363@qq.com
* @Date: 24/12/2021 下午6:05
* @Description
*/
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LoopPrint123 {
//初始值
private int number = 1;
//Lock对象
private Lock lock = new ReentrantLock();
//通知打印1的信号
private Condition con1 = lock.newCondition();
//通知打印2的信号
private Condition con2 = lock.newCondition();
//通知打印3的信号
private Condition con3 = lock.newCondition();
public void print1() {
lock.lock();
try {
//本方法只能打印1,如果不是1就等待
if (number != 1) {
con1.await();//wait()
}
//如果是1,就打印“1”
System.out.println(1);
//打印完“1”之后,去唤醒打印“2”的线程
number = 2;
con2.signal();//notify;
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print2() {
lock.lock();
try {
//本方法只能打印2,如果不是2就等待
if (number != 2) {
con2.await();
}
//如果是2,就打印“2”
System.out.println(2);
//打印完“2”之后,去唤醒打印“3”的线程
number = 3;
con3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print3() {
lock.lock();
try {
//本方法只能打印3,如果不是3就等待
if (number != 3) {
con3.await();
}
//如果是3,就打印“3”
System.out.println(3);
//打印完“3”之后,去唤醒打印“1”的线程
number = 1;
con1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
测试类
package com.geovis.bin.custom.study.wangbin.lock.contion.print;
/**
* @Author: Wangb
* @EMail: 1149984363@qq.com
* @Date: 24/12/2021 下午6:06
* @Description
*/
public class TestLoopPrint123 {
public static void main(String[] args) {
LoopPrint123 loopPrint = new LoopPrint123();
//创建一个线程,用于不断的打印1(调用print1()方法)
new Thread(() -> {
while (true) {
loopPrint.print1();
}
}).start();
//创建一个线程,用于不断的打印2(调用print2()方法)
new Thread(() -> {
while (true) {
loopPrint.print2();
}
}).start();
//创建一个线程,用于不断的打印3(调用print3()方法)
new Thread(() -> {
while (true) {
loopPrint.print3();
}
}).start();
}
}
运行结果如下图:
现在我们来看下Lock
和synchroized
两种加锁方式的区别:
synchronized | Lock | |
---|---|---|
形式 | Java关键字 | 接口 常用的实现类是ReentrantLock |
锁的获取(加锁) | 如果要访问的资源被加锁,则会一直等待,直到该资源解锁。 | 有多种方式 可以使用synchronized类似一直等待;也可以先尝试获取锁,如果被占用就放弃获取,而不再一直等待。 |
锁的释放形式(解锁) | 1. synchronized修饰的方法或者代码块执行完毕。 2.发生异常 | 必须使用unlock()方法释放 一般建议将unlock()写在finally代码块中 |
锁的状态 | 无法判断 | 可以判断 |
中断等待 | 不支持 | 支持 |
通过上表就可以得知,使用synchronized
加锁,当发生死锁时,相互争夺资源的线程就会一直等待。而如果使用Lock
加锁,则可以避免这种情况。
尝试加锁案例
当一个线程加锁后,另一个线程不会一直等待,而会持续尝试5ms,如果一直失败再放弃加锁。
package com.geovis.bin.custom.study.wangbin.lock.contion.lock.lock;
/**
* @Author: Wangb
* @EMail: 1149984363@qq.com
* @Date: 24/12/2021 下午6:35
* @Description
*/
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestTryLock extends Thread {
static Lock lock = new ReentrantLock();
@Override
public void run() {
boolean isLocked = false;
String currentThreadName = Thread.currentThread().getName();
try {
//在5毫秒的时间内,始终尝试加锁
isLocked = lock.tryLock(5, TimeUnit.MILLISECONDS);
System.out.println(currentThreadName + "-尝试加锁: " + isLocked);
if (isLocked) {
//如果尝试成功,则加锁
Thread.sleep(20);
System.out.println(currentThreadName + "-加锁成功");
} else {
System.out.println(currentThreadName + "-加锁失败");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (isLocked) {
System.out.println(currentThreadName + "-解锁");
lock.unlock();
}
}
}
public static void main(String[] args) {
TestTryLock t1 = new TestTryLock();
TestTryLock t2 = new TestTryLock();
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
-
lock()
:立刻获取锁,如果锁已经被占用则等待。 -
tryLock()
尝试获取锁,如果锁已经被占用则返回false
,且不再等待;如果锁处于空闲,则立刻获取锁,并返回true
; -
tryLock(long time, TimeUnit unit)
:与tryLock()
的区别是,该锁会在time
时间内不断尝试。
程序运行结果如下:
中断等待案例
如果线程A使用Lock()
方式加了锁,并且长时间独占使用,那么线程B就会因为长时间都无法获取锁而一直处于等待状态,但是这种等待的状态可能被其他线程中断。
package com.geovis.bin.custom.study.wangbin.lock.contion.lock.lock;
/**
* @Author: Wangb
* @EMail: 1149984363@qq.com
* @Date: 24/12/2021 下午6:44
* @Description
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestInterruptibly {
private Lock myLock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
TestInterruptibly myInter = new TestInterruptibly();
MyThread aThread = new MyThread(myInter);
aThread.setName("A");
MyThread bThread = new MyThread(myInter);
bThread.setName("B");
aThread.start();
bThread.start();
Thread.sleep(1000);
/**
* main线程休眠1秒,A、B线程各休眠3秒; 即当main结束休眠时,A、B仍然在休眠; 因此,main线程会中断B线程的等待
*/
bThread.interrupt();
}
public void myInterrupt(Thread thread) throws InterruptedException {
myLock.lockInterruptibly();
try {
System.out.println(thread.getName() + "-加锁");
Thread.sleep(3000);
} finally {
System.out.println(thread.getName() + "-解锁");
myLock.unlock();
}
}
}
class MyThread extends Thread {
private TestInterruptibly myInter = null;
public MyThread(TestInterruptibly myInter) {
this.myInter = myInter;
}
@Override
public void run() {
try {
myInter.myInterrupt(Thread.currentThread());
} catch (Exception e) {
System.out.println(Thread.currentThread().getName() + "被中断");
}
}
}
程序运行结果如下:
以上就是关于Lock()
方法的内容和三个案例,希望对你有所帮助~