线程通信
需求:创建一个任务类,里面方法有任务A,任务B,任务C三个方法。同时呢,会相应创建三个线程ThreadA,ThreadB,ThreadC分别去调用前面三个任务方法。然后在主方法中,每过50ms就创建一个自动生成任务的线程存入集合中,同时开启三个线程来跑,假如count计数达到了40000就count++计数一次,输出集合中线程个数和count的值。
class Task {
int num;
boolean flagA; // 默认为false
boolean flagB;
boolean flagC;
public void taskA() {
if (!flagA) {
num += 10;
flagA = true;
}
}
public void taskB() {
if (flagA && !flagB) { // B为false和A为true
num *= 20;
flagB = true;
}
}
public void taskC() {
if (flagB && !flagC) { // C为false和B为true
num *= num;
flagC = true;
}
}
}
// 创建三个线程类去实现任务A 任务B 任务C方法
class ThreadA implements Runnable {
private ArrayList<Task> tasks;
public ThreadA(ArrayList<Task> tasks) {
this.tasks = tasks;
}
@Override
public void run() {
while (true) {
System.out.println("ThreadA");
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if(task!=null) {
task.taskA();
}
}
}
}
}
class ThreadB implements Runnable {
private ArrayList<Task> tasks;
public ThreadB(ArrayList<Task> tasks) {
this.tasks = tasks;
}
@Override
public void run() {
while (true) {
System.out.println("ThreadB");
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if(task!=null) {
task.taskB();
}
}
}
}
}
class ThreadC implements Runnable {
private ArrayList<Task> tasks;
public ThreadC(ArrayList<Task> tasks) {
this.tasks = tasks;
}
@Override
public void run() {
while (true) {
System.out.println("ThreadC");
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if(task!=null) {
task.taskC();
}
}
}
}
}
public class test {
// 设置一个存储线程的容器
static ArrayList<Task> tasks = new ArrayList<>();
public static void main(String[] args) {
// 创建一个自动生成任务的线程
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 添加任务
tasks.add(new Task());
System.out.println("已添加:" + tasks.size());
}
}
}.start();
// 启动其他三个线程
new Thread(new ThreadA(tasks)).start();
new Thread(new ThreadB(tasks)).start();
new Thread(new ThreadC(tasks)).start();
// 再生一个监听 任务完成的线程
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int count = 0;
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if (task.num == 40000) {
count++;
}
}
System.err.println("已完成:" + count);
}
}
}.start();
}
}
上述结果输出中是可以输出 “已完成” 这块的,假如我在ThreadA,ThreadB,ThreadC三个线程中都去掉输出语句呢?会发生什么?
// 创建三个线程类去实现任务A 任务B 任务C方法
class ThreadA implements Runnable {
private ArrayList<Task> tasks;
public ThreadA(ArrayList<Task> tasks) {
this.tasks = tasks;
}
@Override
public void run() {
while (true) {
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if(task!=null) {
task.taskA();
}
}
}
}
}
class ThreadB implements Runnable {
private ArrayList<Task> tasks;
public ThreadB(ArrayList<Task> tasks) {
this.tasks = tasks;
}
@Override
public void run() {
while (true) {
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if(task!=null) {
task.taskB();
}
}
}
}
}
class ThreadC implements Runnable {
private ArrayList<Task> tasks;
public ThreadC(ArrayList<Task> tasks) {
this.tasks = tasks;
}
@Override
public void run() {
while (true) {
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if(task!=null) {
task.taskC();
}
}
}
}
}
此时就不会出现这个 “已完成” 这块的值就会是一直为0的。肯定会问为什么是这样的情况呢?
我们不妨来看一下这个System.out.println 中out和println的源码。
// 这里out是打印输出流的常量对象 默认为null
public final static PrintStream out = null;
// 此时,再来看看PrintStream的源码
// 里面有一个方法 println
public void println() {
newLine();
}
// 再追溯newLine()的源码
private void newLine() {
try {
synchronized (this) {
ensureOpen();
textOut.newLine();
textOut.flushBuffer();
charOut.flushBuffer();
if (autoFlush)
out.flush();
}
}
}
// 我们可以发现这原来有一个sychronized代码块,锁住的this就是PrintStream类对象。
所以我们可以大胆猜测一下?是不是因为这个锁使得各个线程停顿那么一些时间从而使得每个线程中的集合数据被更新了呢?答案就是这样的,如果没有加这个输出语句的时候,自动创建任务的线程中是虽然tasks.add(new Task());
但是却没有“通知其他的线程” ,从而tasks就没办法及时更新。也就是上面ThreadA到ThreadC的死循环中的task.size() 没有更新。
我们可以将输出语句换成sychronized(tasks) {} 代替看看是否能输出 “已完成” 这块的值。读者可以自行去试试。(答案是可以的)
但是问题又来了,这样写输出语句和sychronized代码块在上面就很突兀。有没有什么可以代替呢?volatile关键字就来了。线程的可见性就来了!
但是这里又有一个需要注意的地方,我先写一个坑的伪代码
// 下面写的都是前面的原始代码,只不过为了不占篇幅只取其中属性而已
public class test {
// 设置一个存储线程的容器
volatile static ArrayList<Task> tasks = new ArrayList<>();
}
// 创建三个线程类去实现任务A 任务B 任务C方法
class ThreadA implements Runnable {
private ArrayList<Task> tasks;
public ThreadA(ArrayList<Task> tasks) {
this.tasks = tasks;
}
}
class ThreadB implements Runnable {
private ArrayList<Task> tasks;
public ThreadB(ArrayList<Task> tasks) {
this.tasks = tasks;
}
}
class ThreadC implements Runnable {
private ArrayList<Task> tasks;
public ThreadC(ArrayList<Task> tasks) {
this.tasks = tasks;
}
}
高高兴兴以为能解决线程通信问题,然后去运行代码,发现还是不对!因为需要给每个属性都需要加上这个volatile 关键字才行。
// 下面写的都是前面的原始代码,只不过为了不占篇幅只取其中属性而已
public class test {
// 设置一个存储线程的容器
volatile static ArrayList<Task> tasks = new ArrayList<>();
}
// 创建三个线程类去实现任务A 任务B 任务C方法
class ThreadA implements Runnable {
private volatile ArrayList<Task> tasks;
public ThreadA(ArrayList<Task> tasks) {
this.tasks = tasks;
}
}
class ThreadB implements Runnable {
private volatile ArrayList<Task> tasks;
public ThreadB(ArrayList<Task> tasks) {
this.tasks = tasks;
}
}
class ThreadC implements Runnable {
private volatile ArrayList<Task> tasks;
public ThreadC(ArrayList<Task> tasks) {
this.tasks = tasks;
}
}
这样就可以解决上述线程通信的问题啦!