多线程间的通信
- 需求
- synchronized 实现
- JUC Lock 实现
- 多线程指定调用顺序
需求
一般情况下,一个线程只负责自己的任务,执行完成之后就结束了,但是在多线程中,一个线程的处理有可能会依赖另一个线程的数据,所以是需要线程之间的通信,达到信息同步的目的。
举个栗子:多个线程去操作初始值为0的一个变量,实现一个线程对该变量 +1 另一个线程对该变量 -1,交替打印。
在多线程的编程中,参照:在高内聚低耦合的前提下,线程->操作->资源类的结构去做,这样逻辑清晰一点。在资源类中的代码编写,参照 判断 – 业务逻辑 – 通知的结构去做。
synchronized 实现
代码实现 资源类
class Number {
private int num = 0; // 定义一个变量
public int getNum() {
return num;
}
// +1 的方法
public synchronized void increase() {
while (num != 0) { // 使用while是为了防止线程的虚假唤醒
try {
this.wait(); // 线程等待
} catch (InterruptedException e) { // 出现异常简单打印一下
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName() + "\t" + num);
this.notifyAll(); // 唤醒其它所有的线程
}
// -1 的方法
public synchronized void decrease() {
while (num == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + "\t" + num);
this.notifyAll();
}
}
调用.wait()
方法:导致当前线程等待,直到另一个线程调用该对象的notify()
方法或notifyAll()
方法。
调用.notify()
方法:唤醒正在等待对象监视器的一个线程。 如果任何线程正在等待这个对象,其中一个被选择被唤醒。 选择是任意的,并且由实施的判断发生。 线程通过调用wait方法之一等待对象的监视器。唤醒的线程将无法继续,直到当前线程放弃此对象上的锁定为止。 唤醒的线程将以通常的方式与任何其他线程竞争,这些线程可能正在积极地竞争在该对象上进行同步; 例如,唤醒的线程在下一个锁定该对象的线程中没有可靠的权限或缺点。
调用.notifyAll()
方法:唤醒正在等待对象监视器的所有线程。 线程通过调用wait方法之一等待对象的监视器。
测试代码
public static void main(String[] args) {
Number num = new Number();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
num.increase();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
num.decrease();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
num.increase();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
num.decrease();
}
}, "D").start();
}
}
测试结果
JUC Lock 实现
实现代码
class Number1 {
// 锁
private final transient Lock lock = new ReentrantLock();
private final transient Condition condition = lock.newCondition();
private int num = 0;
public int getNum() {
return num;
}
public void increase() {
try {
lock.lock();
while (num != 0) {
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName() + "\t" + num);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrease() {
try {
lock.lock(); // 加锁
while (num == 0) {
condition.await(); // 等待
}
num--;
System.out.println(Thread.currentThread().getName() + "\t" + num);
condition.signalAll(); // 唤醒其它所有线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Lock :Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作
Condition:条件(也称为条件队列或条件变量 )为一个线程暂停执行(“等待”)提供了一种方法,直到另一个线程通知某些状态现在可能为真。 因为访问此共享状态信息发生在不同的线程中,所以它必须被保护,因此某种形式的锁与该条件相关联。 等待条件的关键属性是它原子地释放相关的锁并挂起当前线程,就像Object.wait 。
多线程指定调用顺序
多线程直接按照顺序调用 实现 线程A --> 线程B --> 线程C,启动三个线程要求如下,A打印5次 B打印10次 C打印15次 循环调用10遍,永远保证 A ->B ->C
直接使用Lock实现
public class ThreadOrderAccess {
public static void main(String[] args) {
AccessResource resource = new AccessResource();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.print5();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.print10();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.print15();
}
}, "C").start();
}
}
// 资源类
class AccessResource {
private final Lock lock = new ReentrantLock();
private final Condition c1 = lock.newCondition();
private final Condition c2 = lock.newCondition();
private final Condition c3 = lock.newCondition();
private int num = 1;
public int getNum() {
return this.num;
}
public void print5() {
try {
lock.lock();
// 1、判断
while (num != 1) {
c1.await();
}
// 2、业务流程
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 3、通知
num = 2; // 更换标志位
c2.signal(); // 通知线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10() {
try {
lock.lock();
// 1、判断
while (num != 2) {
c2.await();
}
// 2、业务流程
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 3、通知
num = 3; // 更换标志位
c3.signal(); // 通知线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15() {
try {
lock.lock();
// 1、判断
while (num != 3) {
c3.await();
}
// 2、业务流程
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
// 3、通知
num = 1; // 更换标志位
c1.signal(); // 通知线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition的signal()
方法可以指定唤醒某一个线程。