文章目录
synchronized版
生产者和消费者问题
创建生产者和消费者,使用两个线程
去操作同一个资源!
package demo2;
public class Test {
public static void main(String[] args) {
// 使用多线程,操作同一个类
Data data = new Data();
// 线程A 负责增加
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
// 线程B 负责减少
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
/**
* 数据操作类;<br />
* 实现 num 数据的 +1 和 -1 操作
*/
class Data{
private int num = 0;
// 数据 +1 ,添加 synchronized 实现线程的加锁
public synchronized void increment() throws InterruptedException {
// 如果现在的num是0,则+1,否则等待其他线程的通知
if(num != 0){
// 等待
this.wait();
}
// 否则 +1
num ++ ;
System.out.println(Thread.currentThread().getName()+" num=>"+num);
// 通知其他线程(通知所有)
this.notifyAll();
}
// 数据 -1 操作
public synchronized void decrement() throws InterruptedException {
// 如果现在的num是0,则+1,否则等待其他线程的通知
if(num == 0){
// 等待
this.wait();
}
// 否则 +1
num -- ;
System.out.println(Thread.currentThread().getName()+" num=>"+num);
// 通知其他线程(通知所有)
this.notifyAll();
}
}
当只有两个线程时,此时消费者消费生产者产出的数据时,会得到以下的日志打印信息:
如果此时出现的消费者线程不是两个呢?假设3个、4个?
多个消费者消费同一生产者数据问题
依旧是一个生产者负责数据的+1,-1操作;但此时的消费者则有超过2个!
package demo2;
public class Test2 {
public static void main(String[] args) {
// 使用多线程,操作同一个类
Data2 data2 = new Data2();
// 线程A 负责增加
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
// 线程B 负责减少
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
// 线程C 负责增加
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
// 线程D 负责减少
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
/**
* 数据操作类;<br />
* 实现 num 数据的 +1 和 -1 操作
*/
class Data2{
private int num = 0;
// 数据 +1 ,添加 synchronized 实现线程的加锁
public synchronized void increment() throws InterruptedException {
// 如果现在的num是0,则+1,否则等待其他线程的通知
if(num != 0){
// 等待
this.wait();
}
// 否则 +1
num ++ ;
System.out.println(Thread.currentThread().getName()+" num=>"+num);
// 通知其他线程(通知所有)
this.notifyAll();
}
// 数据 -1 操作
public synchronized void decrement() throws InterruptedException {
// 如果现在的num是0,则+1,否则等待其他线程的通知
if(num == 0){
// 等待
this.wait();
}
// 否则 +1
num -- ;
System.out.println(Thread.currentThread().getName()+" num=>"+num);
// 通知其他线程(通知所有)
this.notifyAll();
}
}
多个线程去操作同一资源时,则此时运行代码(多次运行),会出现以下问题:
多个线程造成的问题分析
在调用 java.lang.Object
类的 wait()
时,几率
出现虚假唤醒
的现象!
按照官方文档给出的建议,此时则需要修改for为while。
public class Test3 {
public static void main(String[] args) {
// 使用多线程,操作同一个类
Data3 data3 = new Data3();
// 线程A 负责增加
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data3.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
// 线程B 负责减少
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data3.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
// 线程C 负责增加
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data3.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
// 线程D 负责减少
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data3.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
/**
* 数据操作类;<br />
* 实现 num 数据的 +1 和 -1 操作<br />
*
* 为了防止 java.lang.Object 中的 wait() 虚假唤醒,需要修改if 为 while。
*/
class Data3{
private int num = 0;
// 数据 +1 ,添加 synchronized 实现线程的加锁
public synchronized void increment() throws InterruptedException {
// 如果现在的num是0,则+1,否则等待其他线程的通知
// 防止虚假等待,修改if为while
while(num != 0){
// 等待
this.wait();
}
// 否则 +1
num ++ ;
System.out.println(Thread.currentThread().getName()+" num=>"+num);
// 通知其他线程(通知所有)
this.notifyAll();
}
// 数据 -1 操作
public synchronized void decrement() throws InterruptedException {
// 如果现在的num是0,则+1,否则等待其他线程的通知
// 防止虚假等待,修改if为while
while(num == 0){
// 等待
this.wait();
}
// 否则 +1
num -- ;
System.out.println(Thread.currentThread().getName()+" num=>"+num);
// 通知其他线程(通知所有)
this.notifyAll();
}
}
问题根源
-
wait() 存在虚假唤醒的情况。
-
if 判断只会判断一次,while中条件为true时为持续判断!
当某个线程执行了this.notifyAll()
,此时如果是if(xxx)
(if只会判断一次)则取消了其中的this.wait()
,执行了下列的流程;但是如果此时是while(xxx)
,则会继续进行判断,当时别到判断条件不符合时,才会继续执行下面的代码。
JUC实现
上面的代码中,为了解决 java.lang.Object.wait()
造成的虚假唤醒
问题,将if 一次判断
更改为while 持续判断
。
使用JUC下的Condition
在java.util.concurrent
包下有更好的解决加锁
、等待
、通知
、释放锁
的操作,看下列栗子:
package demo2_1;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
// 操作同一个资源
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
/**
* 使用 Condition 实现类似Object的wait()和notify()方式,解决wait()虚假唤醒问题
*/
class Data{
private int num = 0;
// 获取一个锁
Lock lock = new ReentrantLock();
// 获取可操作的 Condition 类
Condition condition = lock.newCondition();
// 数据 +1
public void increment() throws InterruptedException {
// 加锁 如果是使用 tryLock(),需要判断是否成功获取到了锁
lock.lock();
try {
// 判断当前的数据是否是0,如果是0则+1,不是则等待
while (num != 0){
// 等待唤醒
condition.await();
}
// 否则 +1
num ++;
System.out.println(Thread.currentThread().getName()+" num = "+num);
// 通知其他所有线程 其他线程很多,直接使用 signalAll
condition.signalAll();
}finally {
// 前面加了锁,此时则需要释放锁,为了保证异常也能释放,需要放在finally中
lock.unlock();
}
}
// 数据 -1
public void decrement() throws InterruptedException {
// 加锁 如果是使用 tryLock(),需要判断是否成功获取到了锁
lock.lock();
try {
// 判断当前的数据是否是0,如果是0则+1,不是则等待
while (num == 0){
// 等待唤醒
condition.await();
}
// 否则 +1
num --;
System.out.println(Thread.currentThread().getName()+" num = "+num);
// 通知其他所有线程 其他线程很多,直接使用 signalAll
condition.signalAll();
}finally {
// 前面加了锁,此时则需要释放锁,为了保证异常也能释放,需要放在finally中
lock.unlock();
}
}
}
运行代码,控制台日志打印信息如下所示:
使用Condition实现精准唤醒
从上面的日志中,可以发现:
使用
Condition
可以实现类似java.lang.Object
中的wait()
和notifyAll()
的操作,使用java.util.concurrent
中的locks.Lock
可以实现类似synchronized
的加锁
和释放锁
的要求。
但,执行的线程很乱,如何达到精准唤醒的操作?
实现:
A执行->唤醒B
B执行->唤醒C
C执行->唤醒D
D执行->唤醒A
实现方式:
采取定义
不同的 Condition 监视器
,执行完指定的逻辑方法后,唤醒对应
的那个监视器。
代码实现:
package demo2_1;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition 精准唤醒的实现方式;<br />
* A执行完成-》唤醒B,B执行完-》唤醒C,C执行完—》唤醒A
*/
public class Test2 {
public static void main(String[] args) {
// 操作同一个资源
Data2 data2 = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.printB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data2.printC();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
/**
* 使用 Condition 实现类似Object的wait()和notify()方式,解决wait()虚假唤醒问题
*/
class Data2{
private int num = 1;
// 获取一个锁
Lock lock = new ReentrantLock();
// 获取可操作的 Condition 类,实现监视器(由于是三个方法,所以创建3个不同的监视器)
// 不同的监视器对应不同的线程,假设 conditionA 对应 线程A
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
public void printA() throws InterruptedException {
// 加锁
lock.lock();
try{
// 判断当前 num属性是否为1,不是则等待
while (num != 1){
// A 自己的监视器等待
conditionA.await();
}
// 否则则将num改为2
num = 2;
System.out.println(Thread.currentThread().getName()+"执行成功,唤醒B");
// 唤醒 B 监视器
conditionB.signal();
}finally {
// 释放锁
lock.unlock();
}
}
public void printB() throws InterruptedException {
// 加锁
lock.lock();
try{
// 判断当前 num属性是否为2,不是则等待
while (num != 2){
conditionB.await();
}
// 否则则将num改为3
num = 3;
System.out.println(Thread.currentThread().getName()+"执行成功,唤醒C");
// 唤醒 C
conditionC.signal();
}finally {
// 释放锁
lock.unlock();
}
}
public void printC() throws InterruptedException {
// 加锁
lock.lock();
try{
// 判断当前 num属性是否为3,不是则等待
while (num != 3){
conditionC.await();
}
// 否则则将num改为2
num = 1;
System.out.println(Thread.currentThread().getName()+"执行成功,唤醒A");
// 唤醒 A
conditionA.signal();
}finally {
// 释放锁
lock.unlock();
}
}
}
执行后的结果: