同步锁的等待唤醒机制及线程交替
日常我们会经常使用同步锁,原先使用synchronized我们了解等待唤醒机制,那么同步锁中该怎么实现。
等待唤醒机制
先看synchronized原先怎么处理。
测试代码
/*
* 生产者和消费者案例
*/
public class TestProductorAndConsumerFake {
public static void main(String[] args) {
ClerkFake clerk = new ClerkFake();
ProductorFake pro = new ProductorFake(clerk);
ConsumerFake cus = new ConsumerFake(clerk);
new Thread(pro, "生产者 A").start();
new Thread(cus, "消费者 B").start();
//
// new Thread(pro, "生产者 C").start();
// new Thread(cus, "消费者 D").start();
}
}
//店员
class ClerkFake{
private int product = 0;
//进货
public synchronized void get(){//循环次数:0
if(product >= 10){//触发虚假唤醒问题
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
}
}else {
System.out.println(Thread.currentThread().getName() + " : " + ++product);
this.notifyAll();
}
}
//卖货
public synchronized void sale(){//product = 0; 循环次数:0
if(product <= 0){
System.out.println("缺货!");
try {
this.wait();
} catch (InterruptedException e) {
}
}else {
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
}
}
//生产者
class ProductorFake implements Runnable{
private ClerkFake clerk;
public ProductorFake(ClerkFake clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
clerk.get();
}
}
}
//消费者
class ConsumerFake implements Runnable{
private ClerkFake clerk;
public ConsumerFake(ClerkFake clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
定义店员,用于执行消费和生产,而且生产和消费是同一个店员。
定义消费者,循环消费产品;定义生产者,循环生产产品。
**等待唤醒机制的目的:**当生产跟不上消费时,避免浪费资源的线程处理消费操作。
this.wait(); //执行等待
this.notifyAll();//唤醒等待线程
问题一:此代码存在无法唤醒的情况
当生产者每次生产间隔200毫秒,存在同时触发消费线程等待和生产线程等待的情况,导致貌似唤醒完成其实进程处于长期等待状态,无法被唤醒
解决:
将唤醒执行与等待线程剥离开来,即与等待条件剥离,保证等待线程被唤醒时可继续执行唤醒操作,保证卖货和买货两个唤醒操作执行可执行一个
//进货
public synchronized void get(){//循环次数:0
if(product >= 1){
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + " : " + ++product);
this.notifyAll();
}
//卖货
public synchronized void sale(){//product = 0; 循环次数:0
if(product <= 0){
System.out.println("缺货!");
try {
this.wait();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
问题二:存在虚假唤醒的情况
目前是一个生产者和一个消费者,不存并发的情况,如果是两个生产者和两个消费者,就会存在出现负数的情况
public static void main(String[] args) {
ClerkIn clerk = new ClerkIn();
ProductorIn pro = new ProductorIn(clerk);
ConsumerIn cus = new ConsumerIn(clerk);
new Thread(pro, "生产者 A").start();
new Thread(cus, "消费者 B").start();
//
new Thread(pro, "生产者 C").start();
new Thread(cus, "消费者 D").start();
}
输出:
生产者 A : -15
生产者 C : -14
生产者 A : -13
生产者 C : -12
生产者 A : -11
生产者 C : -10
生产者 A : -9
生产者 C : -8
生产者 A : -7
生产者 C : -6
生产者 A : -5
生产者 C : -4
生产者 A : -3
生产者 C : -2
生产者 A : -1
生产者 C : 0
原因很简单,就是因为消费线程可能同时被唤醒,从而导致同时被–的情况,出现负数这样错误的结果。
查看jdk源码:
//Object.java
* As in the one argument version, interrupts and spurious wakeups are
* possible, and this method should always be used in a loop://一些版本中,可能出现虚假唤醒的可能,这个方法应该总被使用在循环中while
* <pre>
* synchronized (obj) {
* while (<condition does not hold>)
* obj.wait();
* ... // Perform action appropriate to condition
* }
* </pre>
public final void wait() throws InterruptedException {
wait(0);
}
解决:
源码说的很清楚,需要将if改成while
//进货
public synchronized void get(){//循环次数:0
while(product >= 1){//为了避免虚假唤醒问题,应该总是使用在循环中
System.out.println("产品已满!");
try {
this.wait();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + " : " + ++product);
this.notifyAll();
}
//卖货
public synchronized void sale(){//product = 0; 循环次数:0
while(product <= 0){
System.out.println("缺货!");
try {
this.wait();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + " : " + --product);
this.notifyAll();
}
言归正传,同步锁的唤醒机制其实理解了原先synchronized的方式后,也很容易理解,因为同步锁中是使用condition对象来实现唤醒机制的,而对应Object的await、notify和notifyAll方法,condition中为await、signal和signalAll,在此就不多说了,详见代码
/*
* 生产者消费者案例:
*/
public class TestProductorAndConsumerForLock {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor pro = new Productor(clerk);
Consumer con = new Consumer(clerk);
new Thread(pro, "生产者 A").start();
new Thread(con, "消费者 B").start();
// new Thread(pro, "生产者 C").start();
// new Thread(con, "消费者 D").start();
}
}
class Clerk {
private int product = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// 进货
public void get() {
lock.lock();
try {
if (product >= 1) { // 为了避免虚假唤醒,应该总是使用在循环中。
System.out.println("产品已满!");
try {
condition.await();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + " : "
+ ++product);
condition.signalAll();
} finally {
lock.unlock();
}
}
// 卖货
public void sale() {
lock.lock();
try {
if (product <= 0) {
System.out.println("缺货!");
try {
condition.await();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + " : "
+ --product);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
// 生产者
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
// 消费者
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
线程交替
线程交替这里我们是以一个面试题为例去做的
编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。
如:ABCABCABC……
我们实现原理也是利用同步锁的等待和唤醒机制去实现,详细查看如下代码
public class TestABCAlternate {
public static void main(String[] args) {
AlternateDemo ad = new AlternateDemo();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
ad.loopA(i);
}
}
}, "A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
ad.loopB(i);
}
}
}, "B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
ad.loopC(i);
System.out.println("-----------------------------------");
}
}
}, "C").start();
}
}
class AlternateDemo{
private int number = 1; //当前正在执行线程的标记
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
/**
* @param totalLoop : 循环第几轮
*/
public void loopA(int totalLoop){
lock.lock();
try {
//1. 判断
if(number != 1){
condition1.await();
}
//2. 打印
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName() + "t" + i + "t" + totalLoop);
}
//3. 唤醒
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void loopB(int totalLoop){
lock.lock();
try {
//1. 判断
if(number != 2){
condition2.await();
}
//2. 打印
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName() + "t" + i + "t" + totalLoop);
}
//3. 唤醒
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void loopC(int totalLoop){
lock.lock();
try {
//1. 判断
if(number != 3){
condition3.await();
}
//2. 打印
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName() + "t" + i + "t" + totalLoop);
}
//3. 唤醒
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
输出:
A 1 1
B 1 1
C 1 1
-----------------------------------
A 1 2
B 1 2
C 1 2
-----------------------------------
A 1 3
B 1 3
C 1 3
-----------------------------------
A 1 4
B 1 4
C 1 4
-----------------------------------
A 1 5
B 1 5
C 1 5
-----------------------------------
A 1 6
B 1 6
C 1 6
-----------------------------------
A 1 7
B 1 7
C 1 7
-----------------------------------
A 1 8
B 1 8
C 1 8
-----------------------------------
A 1 9
B 1 9
C 1 9
-----------------------------------
A 1 10
B 1 10
C 1 10
-----------------------------------
A 1 11
B 1 11
C 1 11
-----------------------------------
A 1 12
B 1 12
C 1 12
-----------------------------------
A 1 13
B 1 13
C 1 13
-----------------------------------
A 1 14
B 1 14
C 1 14
-----------------------------------
A 1 15
B 1 15
C 1 15
-----------------------------------
A 1 16
B 1 16
C 1 16
-----------------------------------
A 1 17
B 1 17
C 1 17
-----------------------------------
A 1 18
B 1 18
C 1 18
-----------------------------------
A 1 19
B 1 19
C 1 19
-----------------------------------
A 1 20
B 1 20
C 1 20
-----------------------------------
原文始发于微信公众号(云户):同步锁的等待唤醒机制及线程交替
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/25938.html