• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
Y-wee
博客园    首页    新随笔    联系   管理     

线程间通信

线程间通信

线程间通信是通过共享内存和消息传递来实现的,下面通过代码实战演示线程间是如何实现通信的

场景:两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1;通过线程通信实现数值始终维持在0或1

synchronized 实现线程间通信

资源类

package com.yl.entity;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * 线程资源
 *
 * @author Y-wee
 */
@Getter
@Setter
@ToString
public class Number {
    /**
     * 线程操作具体资源
     */
    private Integer number = 0;

    /**
     * number加1
     *
     * @throws InterruptedException
     */
    public synchronized void increment() throws InterruptedException {
        if (number != 0) {
            this.wait();
        }
        number++;

        // 唤醒所有等待线程
        this.notifyAll();

        System.out.println(Thread.currentThread().getName()+",number++,number="+number);
    }

    /**
     * number减1
     *
     * @throws InterruptedException
     */
    public synchronized void reduce() throws InterruptedException {
        if (number == 0) {
            this.wait();
        }
        number--;

        // 唤醒所有等待线程
        this.notifyAll();

        System.out.println(Thread.currentThread().getName()+",number--,number="+number);
    }
}

启动两个线程对number进行加减操作

package com.yl.communication;

import com.yl.entity.Number;

/**
 * 启动类
 *
 * @author Y-wee
 */
public class Sync {

    public static void main(String[] args) {
        Number number = new Number();

        new Thread(() -> {
            for (int i = 0; i < 7; i++) {
                try {
                    number.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程A").start();

        new Thread(() -> {
            for (int i = 0; i < 7; i++) {
                try {
                    number.reduce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程B").start();
    }

}

以上两个线程间通信实现了number始终维持在0或1,但是如果在此基础之上增加线程,就会产生虚假唤醒问题,导致number值混乱:

假设number初始值为0,现在有四个线程A、B、C、D,A和B线程对number加一,C和D对number减一

  • 启动四个线程,A先抢到资源(B、C、D已经处于等待状态),对number加1,然后唤醒B、C、D
  • 此时B抢到资源,对number加一,number变成2

问题:B 抢到了资源不是会先进行判断吗,如果number不等于0则不进行加一操作

解答:wait 方法有一个特点:在哪里等待,在哪里醒来;也就是说B线程等待时已经判断过了,被唤醒后不用再判断(if 只需要判断一次),这也就导致了虚假唤醒

优化方案:通过 while 将判断放在循环里,这样线程每次被唤醒抢到资源都必须要经过判断才能对number进行操作,避免虚假唤醒

资源类

package com.yl.entity;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * 线程资源
 *
 * @author Y-wee
 */
@Getter
@Setter
@ToString
public class Number {
    /**
     * 线程操作具体资源
     */
    private Integer number = 0;

    /**
     * number加1
     *
     * @throws InterruptedException
     */
    public synchronized void increment() throws InterruptedException {
        while (number != 0) {
            this.wait();
        }
        number++;

        // 唤醒所有等待线程
        this.notifyAll();

        System.out.println(Thread.currentThread().getName()+",number++,number="+number);
    }

    /**
     * number减1
     *
     * @throws InterruptedException
     */
    public synchronized void reduce() throws InterruptedException {
        while (number == 0) {
            this.wait();
        }
        number--;

        // 唤醒所有等待线程
        this.notifyAll();

        System.out.println(Thread.currentThread().getName()+",number--,number="+number);
    }
}

Lock 实现线程间通信

上面通过 synchronized 实现线程间通信,Lock也同样可以实现,下面演示Lock实现方式

资源类

package com.yl.entity;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程资源
 *
 * @author Y-wee
 */
@Getter
@Setter
@ToString
public class NumberLock {
    /**
     * 线程操作具体资源
     */
    private Integer number = 0;
    /**
     * 锁
     */
    private Lock lock=new ReentrantLock();
    /**
     * Lock实现线程通信对象
     */
    private Condition condition=lock.newCondition();

    /**
     * number加1
     *
     * @throws InterruptedException
     */
    public void increment() throws InterruptedException {
        lock.lock();

        try {
            while (number != 0) {
                condition.await();
            }
            number++;

            // 唤醒所有等待线程
            condition.signalAll();

            System.out.println(Thread.currentThread().getName()+",number++,number="+number);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * number减1
     *
     * @throws InterruptedException
     */
    public void reduce() throws InterruptedException {
        lock.lock();

        try {
            while (number == 0) {
                condition.await();
            }
            number--;

            // 唤醒所有等待线程
            condition.signalAll();

            System.out.println(Thread.currentThread().getName()+",number--,number="+number);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

启动四个线程

package com.yl.communication;

import com.yl.entity.NumberLock;

/**
 * 启动类
 *
 * @author Y-wee
 */
public class LockMain {

    public static void main(String[] args) {
        NumberLock numberLock = new NumberLock();

        new Thread(() -> {
            for (int i = 0; i < 7; i++) {
                try {
                    numberLock.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程A").start();

        new Thread(() -> {
            for (int i = 0; i < 7; i++) {
                try {
                    numberLock.reduce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程B").start();

        new Thread(() -> {
            for (int i = 0; i < 7; i++) {
                try {
                    numberLock.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程C").start();

        new Thread(() -> {
            for (int i = 0; i < 7; i++) {
                try {
                    numberLock.reduce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程D").start();
    }
}

Condition 唤醒指定线程

场景:启动三个线程:线程A设置number=1,线程B设置number=2,线程B设置number=3;三个线程依次执行七次

资源类

package com.yl.entity;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程资源
 *
 * @authorY-wee
 */
@Getter
@Setter
@ToString
public class ResourceNumber {
    /**
     * 线程操作具体资源
     */
    private Integer number = 0;

    private Lock lock = new ReentrantLock();

    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();
    private Condition conditionC = lock.newCondition();

    /**
     * 设置number=1
     *
     * @throws InterruptedException
     */
    public void numberA() throws InterruptedException {
        lock.lock();

        try {
            while (number == 1) {
                // 当前线程等待
                conditionA.await();
            }
            number = 1;
            System.out.println(Thread.currentThread().getName() + "number=" + number);

            // 唤醒conditionB的等待线程
            conditionB.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 设置number=2
     *
     * @throws InterruptedException
     */
    public void numberB() throws InterruptedException {
        lock.lock();

        try {
            while (number == 2) {
                // 当前线程等待
                conditionB.await();
            }
            number = 2;
            System.out.println(Thread.currentThread().getName() + "number=" + number);

            // 唤醒conditionC的等待线程
            conditionC.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 设置number=3
     *
     * @throws InterruptedException
     */
    public void numberC() throws InterruptedException {
        lock.lock();

        try {
            while (number == 3) {
                // 当前线程等待
                conditionC.await();
            }
            number = 3;
            System.out.println(Thread.currentThread().getName() + "number=" + number);

            // 唤醒conditionA的等待线程
            conditionA.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

启动三个线程

package com.yl.communication;

import com.yl.entity.NumberLock;
import com.yl.entity.ResourceNumber;

/**
 * 启动类
 *
 * @author Y-wee
 */
public class ResourceMain {

    public static void main(String[] args) {
        ResourceNumber resourceNumber = new ResourceNumber();

        new Thread(() -> {
            for (int i = 0; i < 7; i++) {
                try {
                    resourceNumber.numberA();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程A").start();

        new Thread(() -> {
            for (int i = 0; i < 7; i++) {
                try {
                    resourceNumber.numberB();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程B").start();

        new Thread(() -> {
            for (int i = 0; i < 7; i++) {
                try {
                    resourceNumber.numberC();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程C").start();

    }

}
记得快乐
posted @ 2022-05-22 21:41  Y-wee  阅读(54)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3