一、经典问题:生产者与消费者
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()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。