线程之间的通讯

一、经典问题:生产者与消费者

1.1 共享资源

class Res{

    public String name;
    public String sex;
}

1.2 生产者

class InThread extends Thread {
	private Res res;

	public InThread(Res res) {
		this.res = res;
	}

	@Override
	public void run() {
		int count = 0;
		while (true) {
				if (count == 0) {
					res.userName = "李四";
					res.userSex = "男";
				} else {
					res.userName = "王五";
					res.userSex = "女";
				}
				count = (count + 1) % 2;
			}
	}
}

1.3 消费者

class OutThread extends Thread {
	private Res res;

	public OutThread(Res res) {
		this.res = res;
	}

	@Override
	public void run() {
		while (true) {
		   System.out.println("姓名:" + res.name + "," + "性别:" + res.sex);
		}
	}
}

1.4 问题

数据发生错乱,造成线程安全问题。

1.5 解决线程安全问题

加锁就可以了

生产者加锁

class InThread extends Thread{

    public Res res;

    public InThread(Res res) {
        this.res = res;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            synchronized (res) {
                if (count == 0) {
                    res.name = "李四";
                    res.sex = "女";
                } else {
                    res.name = "王五";
                    res.sex = "男";
                }
                count = (count + 1) % 2;
            }
        }
    }
}

消费者加锁

class OutThread extends Thread{

    public Res res;

    public OutThread(Res res) {
        this.res = res;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (res) {
                System.out.println("姓名:" + res.name + "," + "性别:" + res.sex);
            }
        }
    }
}

此时不会再出现线程安全问题,但是与预期结果还有差异,预期结果是生成一个消费一个,现在加了synchronized后,由于CPU调度,读的线程会多次调用,所以可能会连续读同一个:

1.6 解决线程通讯问题

wait:暂停当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行

notify: 唤醒当前对象锁池被等待线程,使之运行。

因为涉及到对象锁,所以它们必须都放在synchronized中来使用. wait、notify一定要在synchronized里面进行使用,并且对应同一个锁资源。

修改后的代码:

package com.thread.communication;

/**
 * @Author: 98050
 * @Time: 2018-12-05 23:19
 * @Feature:
 */

/**
 * 共享对象
 */
class Res{

    public String name;
    public String sex;

    /**
     * true 允许读,不能写
     * false 允许写,不能读
     */
    public Boolean flag = false;

}

/**
 * 写入线程
 */
class InThread extends Thread{

    public Res res;

    public InThread(Res res) {
        this.res = res;
    }

    @Override
    public void run() {
        int count = 0;
        while (true){
            synchronized (res) {
                if (res.flag){
                    try {
                        res.wait(); //释放当前锁对象
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                if (count == 0) {
                    res.name = "李四";
                    res.sex = "女";
                } else {
                    res.name = "王五";
                    res.sex = "男";
                }
                count = (count + 1) % 2;
                res.flag = true; //标记当前线程为等待
                res.notify(); //唤醒被等待的线程
            }
        }
    }
}

/**
 * 读取线程
 */
class OutThread extends Thread{

    public Res res;

    public OutThread(Res res) {
        this.res = res;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (res) {
                try {
                    if (!res.flag){
                        res.wait();
                    }
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("姓名:" + res.name + "," + "性别:" + res.sex);
                res.flag = false;//标记当前线程为等待
                res.notify(); //唤醒被等待的线程
            }
        }
    }
}

public class Test001 {

    public static void main(String[] args) {
        Res res = new Res();
        InThread inThread = new InThread(res);
        OutThread outThread = new OutThread(res);
        inThread.start();
        outThread.start();
    }
}

效果:

二、知识点

2.1 wait与join的区别

join的功能:在线程A中调用线程B的join方法,那么只有等线程B执行完后A才会继续执行。

join的源码:

public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

注意这是一个用synchronized修饰的方法,所以它是一个同步方法。

逻辑:

  • 判断参数时间参数,如果参数小于0,抛出IllegalArgumentException("timeout value is negative")异常
  • 参数等于0,判断调用join的线程(假设是A)是否存活,不存活就不执行操作,如果存活,就调用wait(0),阻塞join方法,等待A线程执行完在结束join方法。
  • 参数大于0,判断调用join的A线程是否存活,不存活就不执行操作,如果存活,就调用wait(long millis),阻塞join方法,等待时间结束再继续执行join方法。

举个例子,现有两个线程A与B,A中调用了B.join(),那么A线程会停止,等B先运行完后A才开始运行。那么有人会想当然的理解成,B.join()等同于B.wait(),不就是B在wait,也就是B等待吗,为什么反而会让A停运,让B先运行呢?要理解这里,先假设一个对象D,需要知道其实wait()方法是D.wait(),也就是说获取了对象D的对象锁的线程释放该对象锁,重新进入对象的锁获取队列中,与其他线程一起竞争该锁。主线程A看作是调用B对象的线程,在线程对象B被A线程调用了join后,A线程会释放该对象的对象锁,进入该对象的锁获取队列中,等待唤醒(例如该对象执行结束),这样A线程才能继续执行下去,这也是为什么join能控制线程执行顺序的原因所在。

由于join是synchronized修饰的同步方法,因此会出现join(long millis)阻塞时间超过了millis的值。

join方法内部是通过wait进行阻塞的,所以join和wait都会释放锁。而sleep不释放锁,sleep的锁是当前线程对象。

释放锁和不释放锁的区别:释放锁后,该对象同步方法可被其他对象异步调用,而不释放锁则该对象其他同步方法被调用时会进入等待获得锁。

wait和join唤醒后,需要重新获得锁。

 

2.2 wait与sleep的区别

  • 对于sleep()方法,首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
  • sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
  • 在调用sleep()方法的过程中,线程不会释放对象锁。
  • 而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值