Java多线程之间的通信,最常见的就是生产者-消费者模式,通过一个状态码来控制什么时候去生产和消费,下面我们就来写一个简单的例子,来看看这种最常见的线程之间的通信方式
package thread3;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 对同一个数字,两个线程,分别+1,-1,交替执行5次
*/
public class Customer {
static class ShareData {
private int number = 0;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void increment() {
try {
lock.lock();
// 首先判断是否需要生产
while (number != 0) {
// 等待,不能生产
condition.await();
}
// 生产
number++;
System.out.println(Thread.currentThread().getName() + '\t' + number);
// 通知唤醒
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() {
try {
lock.lock();
// 首先判断是否需要生产
while (number == 0) {
// 等待,不能生产
condition.await();
}
// 生产
number--;
System.out.println(Thread.currentThread().getName() + '\t' + number);
// 通知唤醒
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.increment();
}
}, "a").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
shareData.decrement();
}
}, "b").start();
}
}
上面这段代码很好理解,首先有两个线程,a和b,分别执行5次,对ShareData对象中的number值进行加和减,我们实现的方式也很简单,首先给加和减两个方法添加了一个可重入锁,如果number值为0,那么我们就对number值进行加1,否则就对number值进行减1,这个地方加了锁以后,就可以保证同时间只有一个线程可以对number操作,而不会出现多个线程同时操作同一个对象,导致最终的值不一致的情况
这里顺便提一个我遇到的一个面试题:
sychronized和Lock有什么区别?用Lock有什么好处?请举例~
使用方式:
synchronized (new Object()) {}
new ReentrantLock();
1、sychronized是一个关键字,Lock是jdk1.5以后提出的一个类
2、sychronized不用手动释放,Lock需要unlock,而且最好是在finally里面执行
3、sychronized不能中断,除非抛异常或者运行完成,Lock可以中断(设置超时方法:tryLock(long timeout,TimeUnit unit),lockInterruptibly()代码块中,调用interrupt()方法可以中断)
4、sychronized默认非公平锁,Lock默认也是非公平锁,但是可以设置为公平锁
5、ReentrantLock可以用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像sychronized要么随机唤醒一个线程要么唤醒全部线程
关于上面答案的第5点,我们可以举个例子来证明一下,ReentrantLock可以精准的唤醒我们需要唤醒的线程
package thread3;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 多线程之间按顺序调用,实现A->B->C三个线程启动,要求如下:
* A打印5次,B打印10次,C打印15次
* 紧接着
* A打印5次,B打印10次,C打印15次
*/
class ShareData {
private int number = 1; // A:1 B:2 C:3
private final Lock lock = new ReentrantLock();
private final Condition condition1 = lock.newCondition();
private final Condition condition2 = lock.newCondition();
private final Condition condition3 = lock.newCondition();
public void print5() {
lock.lock();
try {
while (number != 1) {
condition1.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
number = 2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10() {
lock.lock();
try {
while (number != 2) {
condition2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
number = 3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15() {
lock.lock();
try {
while (number != 3) {
condition3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
number = 1;
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class LockDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
shareData.print5();
}
}, "a").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
shareData.print10();
}
}, "b").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
shareData.print15();
}
}, "c").start();
}
}
这里没有截图完,但是可以看出来,是按照我们想要的顺序去执行的
下面我们再用阻塞队列再来改造一下:
package thread3;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyResource {
private boolean FLAG = true; // 如果为true的时候,则默认生产和消费
private final AtomicInteger atomicInteger = new AtomicInteger();
BlockingQueue<String> blockingQueue;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void myProd() {
String data;
boolean returnVal;
while (FLAG) {
data = String.valueOf(atomicInteger.incrementAndGet());
try {
returnVal = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (returnVal) {
System.out.println(Thread.currentThread().getName() + "\t 插入队列" + data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t 插入队列" + data + "失败");
}
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "\t 当前生产者停止生产,flag为false");
}
public void myCustomer() {
String data;
while (FLAG) {
try {
data = blockingQueue.poll(2L, TimeUnit.SECONDS);
if (data == null || "".equalsIgnoreCase(data)) {
FLAG = false;
System.out.println(Thread.currentThread().getName() + "\t" + "超过2秒没有可以消费的物品,消费退出");
return;
}
System.out.println(Thread.currentThread().getName() + "\t" + data + "消费队列成功");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "\t 当前消费者停止消费,flag为false");
}
public void stop() {
this.FLAG = false;
}
}
public class BlockQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(10);
MyResource myResource = new MyResource(blockingQueue);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t生产线程开始启动");
myResource.myProd();
}, "prod").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t消费线程开始启动");
myResource.myCustomer();
}, "customer").start();
try {
TimeUnit.SECONDS.sleep(5);
System.out.println("5秒时间到,停止生产和消费");
myResource.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}