多线程通讯
线程之间的通信机制有两种:共享内存和消息传递。
在共享内存的并发模型里,线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过发送消息来显式进行通信。
共享内存
多线程之间以共享内存方式通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同。第一个线程写入(input)用户,另一个线程取读取(out)用户.实现读一个,写一个操作。通过synchronized锁确定线程安全。
代码如下:
class ShareData{
private int number = 0;
public synchronized void sendEmail() throws InterruptedException {
System.out.println(" send email");
}
public synchronized void sendSms() throws InterruptedException {
System.out.println(" send sms");
}
}
public class ThreadDemo {
public static void main(String[] args) {
ShareData sd = new ShareData();
new Thread(()->{
sd.sendEmail();
},"A").start();
new Thread(()->{
sd.sendSms();
},"B").start();
}
}
消息传递
消息传递的核心就是多个线程轮流操作资源类,过程涉及两个核心概念:
- 同一个资源类;
- 状态判断机制即依据资源状态获取资源操作权利;
wait、notify方法
典型的线程操作同变量的场景是生产者和消费者,为保证线程安全可以使用wait、notify方式,使用需要遵守下列原则:
- 因为涉及到对象锁,他们必须都放在synchronized中来使用. Wait、Notify一定要在synchronized里面进行使用。
- Wait必须暂定当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行
- notify/notifyall: 唤醒因锁池中的线程,使之运行
注意:一定要在线程同步中使用,并且是同一个锁的资源
代码
class ShareData{
private int number = 0;
public synchronized void increment() throws InterruptedException {
//判断
if (number!=0){
this.wait();
}
++number;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//唤醒
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if (number==0){
this.wait();
}
--number;
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
ShareData sd = new ShareData();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
sd.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
sd.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
wait与sleep区别
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
虚假唤醒
在上述案例中,如果存在多个生产这同时生产数据,代码如下
public class ThreadDemo2 {
public static void main(String[] args) {
ShareData sd = new ShareData();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
sd.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
sd.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
sd.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 1; i <=10 ; i++) {
try {
sd.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
运行结果:
生产和消费没有交替出现,原因在于资源状态判断时使用的是if,会造成连续两个生产线程进入wait等待区,在消费数据后,连续生产数据。如下图所示:
解决方式:将IF判断改成while判断,即每次线程获取到资源操作权限后,重新判断资源状态
class ShareData{
private int number = 0;
public synchronized void increment() throws InterruptedException {
//判断
while (number!=0){ //将if该换成while
this.wait();
}
++number;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//唤醒
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while (number==0){
this.wait();
}
--number;
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();
}
}
定制化通信
1 创建多个Condition;
2 每个Condition代表一个线程;
3 设置标志位,依据标志位确定哪个Condition被激活;