简单通信
场景---四个线程:
分别为A,B,C,D,其中A,C线程做+1操作,B,D线程做-1操作,想要的结尾应该是A,C线程输出值为1,B,D线程输出值为0(要求 用线程间通信)
synchronized 实现
关键字 synchronized
与 wait()/notify()
这两个方法一起使用可以实现等待/通知模式
操作线程的时候,等待线程使用wait()
通知另外的线程操作用notify()、notifyAll()
假设有两个线程,该线程在执行过程中,判断值(不是该值等待,让其他线程抢),操作值,通知另外一个线程的调度
在线程等待和唤醒时,有一个非常重要的点:
查找 JDK1.8 文档,在 Object
的 wait()
方法中有如下介绍在一个参数版本中,中断和虚假唤醒是可能的,并且该方法应该始终在 循环 中使用
所谓虚假唤醒,就是 wait()方法的一个特点,总结来说 wait() 方法使线程在哪里睡就在哪里醒
在这种情况下,当 A 进入临界区,BCD三个线程在 if 判断后进入 wait() 等待,当A线程完成操作,此时 number 值为1,notifyAll() 会随机唤醒一个线程。
现在C被唤醒,由于 wait() 方法使线程在哪里睡就在哪里醒,所以接下来C在执行时不会再通过 if 判断而是直接+1,此时 number 就是2。从而导致和我们预想的不一致
正确实现代码:
// 创建一个资源类
class Share{
// 设置临界资源
private int number = 0;
// 实现+1操作
public synchronized void incr() throws InterruptedException {
// 操作:判断、干活、通知
if (number != 0) {
// number不为0,等待
this.wait();
}
number++;
System.out.print(Thread.currentThread().getName()+"::"+number+"--->");
// 唤醒其他线程
this.notifyAll();
}
// 实现-1操作
public synchronized void decr() throws InterruptedException {
// 操作:判断、干活、通知
if (number != 1) {
// number不为0,等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"::"+number);
this.notifyAll();
}
}
public class InterThreadCommunication {
public static void main(String[] args) {
Share share = new Share();
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
share.incr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
share.decr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
share.incr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
share.decr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
Lock 实现
关键字 synchronized 与 wait()/notify()这两个方法一起使用可以实现等待/通 知模式
Lock 锁的 newContition()方法返回 Condition 对象,Condition 类 也可以实现等待/通知模式。
用 notify()通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类可以 进行选择性通知, Condition 比较常用的两个方法:
- await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重 新获得锁并继续执行。
- signal()用于唤醒一个等待的线程。
==注意:在调用 Condition 的 await()/signal()方法前,也需要线程持有相关 的 Lock 锁,调用 await()后线程会释放这个锁,在 singal()调用后会从当前 Condition 对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦 获得锁成功就继续执行。==
实例代码如下,主要改动的是资源类,main方法中代码不变
class Share {
// 设置临界资源
private int number = 0;
// 创建一个Com
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
// 实现+1操作
public void incr() {
// 上锁
lock.lock();
try {
// 判断
while (number != 0) {
condition.await();
}
// 干活
number++;
System.out.print(Thread.currentThread().getName() + "::" + number + "--->");
// 通知
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 实现-1操作
public void decr() throws InterruptedException {
// 上锁
lock.lock();
try {
// 判断
while (number != 1) {
condition.await();
}
// 干活
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
// 通知
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
定制化线程通信
案列:启动三个线程,按照如下要求:
AA打印5此,BB打印10次,CC打印15次,一共进行10轮
具体思路:
每个线程添加一个标志位,是该标志位则执行操作,并且修改为下一个标志位,通知下一个标志 位的线程
创建一个可重入锁 private Lock lock = new ReentrantLock();
分别创建三个开锁通知 private Condition c1 = lock.newCondition();(他们能实现指定唤醒)
(注意)具体资源类中的A线程代码操作
上锁,(执行具体操作(判断、操作、通知),解锁)放于try、finally,具体代码如下
class Share{
private int flag = 1;
private Lock lock = new ReentrantLock();
// 创建三个Comdition对象,为了定向唤醒相乘
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
public void Aprint(int loop) {
//上锁
lock.lock();
try{
// 判断
while(flag!=1) {
c1.await();
}
// 干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " ::本次第" + i + "次打印,是第" + loop+ "次循环");
}
flag = 2; //修改标志位,定向唤醒 线程b
// 唤醒
c2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
public void Bprint(int loop) {
//上锁
lock.lock();
try{
// 判断
while(flag!=2) {
c2.await();
}
// 干活
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + " ::本次第" + i + "次打印,是第" + loop+ "次循环");
}
flag = 3; //修改标志位,定向唤醒 线程b
// 唤醒
c3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
public void Cprint(int loop) {
//上锁
lock.lock();
try{
// 判断
while(flag!=3) {
c3.await();
}
// 干活
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + " ::本次第" + i + "次打印,是第" + loop+ "次循环");
}
flag = 1; //修改标志位,定向唤醒 线程b
// 唤醒
c1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
}
public class CustomInterThreadCommunication {
public static void main(String[] args) {
Share share = new Share();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
share.Aprint(i);
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
share.Bprint(i);
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
share.Cprint(i);
}
}
},"C").start();
}
}