我是大白(●—●),这是我开始学习记录大白Java软件攻城狮晋升之路的第十天。 今天学习的是【尚硅谷】大厂必备技术之JUC并发编程
一、线程通信概述和案例分析
1. 多线程编程步骤(中部)
- 第一步:创建资源类,在资源类创建属性和操作方法
- 第二步:在资源类操作方法
- 判断
- 干活
- 通知
- 第三步:创建多个线程,调用资源类的操作方法
- 第四步:防止虚假唤醒问题。
2. 例子
有两个线程,实现对一个初始值是0的变量。一个线程对值+1,另外一个线程对值-1,达到交替执行的效果。
通过Object类的wait()和notify()或者notifyAll()方法实现
wait():在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待。
notify():唤醒在此对象监视器上等待的单个线程。
notifyAll(): 唤醒在此对象监视器上等待的所有线程。
3. Synchronized实现案例
package com.example.demo;
//第一步 创建资源类,定义属性和操作方法
class Share {
private int number = 0;
/**
* 加一的方法
* @throws InterruptedException
*/
public synchronized void incr() throws InterruptedException {
//第二步 判断 干活 通知
if (number != 0) { //判断number的值是否为0,如果不是0,线程等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
this.notifyAll();
}
/**
* 减一的方法
* @throws InterruptedException
*/
public synchronized void decr() throws InterruptedException {
//判断
if (number != 1) {
this.wait();
}
//干活
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知其它线程
this.notifyAll();
}
}
public class ThreadDemo1 {
//第三步 创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"AA").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"BB").start();
}
}
4. 虚假唤醒问题
当新增了两个线程进行加1减1操作后,代码如下所示:
package com.example.demo;
//第一步 创建资源类,定义属性和操作方法
class Share {
private int number = 0;
/**
* 加一的方法
* @throws InterruptedException
*/
public synchronized void incr() throws InterruptedException {
//第二步 判断 干活 通知
if (number != 0) { //判断number的值是否为0,如果不是0,线程等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
this.notifyAll();
}
/**
* 减一的方法
* @throws InterruptedException
*/
public synchronized void decr() throws InterruptedException {
//判断
if (number != 1) {
this.wait();
}
//干活
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知其它线程
this.notifyAll();
}
}
public class ThreadDemo1 {
//第三步 创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"AA").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"BB").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"CC").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"DD").start();
}
}
最终的结果将会是这样:
这就是虚假唤醒的情况,当某一时刻C线程进行+1操作后调用了notifyAll()方法,ABD线程均被唤醒且不需要进行判断,因此A线程又执行了+1操作,导致number的值变为了2的情况。
即当一定的条件触发时会唤醒很多在阻塞态的线程,但只有部分的线程唤醒是有用的,其余线程的唤醒是多余的。
5. 解决虚假唤醒问题
官方jdk中推荐的一种方法用来解决虚假唤醒的方法,即将if改为while循环(具体实例可看下一节Lock实现案例代码):
synchronized (obj) {
while (<condition does not hold>)
{
obj.wait(timeoutMillis, nanos);
} ... // Perform action appropriate to condition or timeout
}
6. Lock实现案例
使用的主要是Condition接口中的await()和singal()或者singalAll()方法进行通信
await():造成当前线程在接到信号或被中断之前一直处于等待状态。
singal():唤醒一个等待线程。
singalAll():唤醒所有等待线程。
package com.example.demo;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//第一步 创建资源类,定义属性和操作方法
class LShare {
private int number = 0;
//创建Lock
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
/**
* 加一的方法
* @throws InterruptedException
*/
public void incr() throws InterruptedException {
lock.lock();
try{
//第二步 判断 干活 通知
while (number != 0) { //判断number的值是否为0,如果不是0,线程等待
condition.await();;
}
number++;
System.out.println(Thread.currentThread().getName() + "::" + number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 减一的方法
* @throws InterruptedException
*/
public synchronized void decr() throws InterruptedException {
lock.lock();
try{
//判断
while (number != 1) {
condition.await();
}
//干活
number--;
System.out.println(Thread.currentThread().getName() + "::" + number);
//通知其它线程
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ThreadDemo2 {
//第三步 创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
LShare share = new LShare();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.incr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "CC").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
share.decr();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "DD").start();
}
}
二、线程间定制化通信
package com.example.demo;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 多线程之间按顺序调用,实现A->B->C三个线程启动,要求如下:
* AA打印5次,BB打印10次,CC打印15次
* 紧接着
* AA打印5次,BB打印10次,CC打印15次
* 。。。。
* 来10轮
*/
class ShareResource {
//定义标志位
private int flag = 1;
//创建Lock锁
private Lock lock = new ReentrantLock();
//创建三个Condition,相当于创建三把钥匙
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
//打印5次,参数第几轮
public void print5(int loop) {
lock.lock();
try {
//1.判断
while (flag != 1) {
c1.await(); //等待
}
//2.干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + ",轮数:" + loop);
}
//修改标志位
flag = 2;
//3.通知BB线程
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10(int loop) {
lock.lock();
try {
while (flag != 2) {
c2.await();
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + ",轮数:" + loop);
}
flag = 3;
c3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15(int loop) {
lock.lock();
try {
while (flag != 3) {
c3.await();
}
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + ",轮数:" + loop);
}
flag = 1;
c1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class SyncAndReentrantLockDemo {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print5(i);
}
}, "AA").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print10(i);
}
}, "BB").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print15(i);
}
}, "CC").start();
}
}