本文目的
现在有一个小需求,需要启两个线程A和B,线程B负责让队列里加元素(加100个),线程A实时追踪线程B的操作,读出队列的元素个数。最后线程A(读到队列中有100个后)先结束,然后线程B结束。
code
package learningThreads.exercises.taobao1;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/* 启动两个线程,线程A负责往队列里添加数据,线程B负责实时报出队列中的元素个数 */
public class ThreadVisibilityTest {
List list = new ArrayList();
public void add(Object o) {
list.add(o);
}
public int size() {
return list.size();
}
public static void main(String[] args) {
ThreadVisibilityTest tvt = new ThreadVisibilityTest();
final Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
while (true) {
System.out.println("from A: " + tvt.size());
if (tvt.size() == 100) {
lock.notify();
System.out.println("线程A结束");
break;
}
try {
lock.notify();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// try {
// System.out.println("睡了1s...");
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
}
}, "线程A").start();
try {
// System.out.println("睡了2s...");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("睡了2s...");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
synchronized (lock) {
while (true) {
if (tvt.size() < 100) {
tvt.add(new Object());
System.out.println("from B: add one");
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
System.out.println("线程B结束");
}, "线程B").start();
}
}
输出
from A: 0 //如果有sleep(1)那块代码,这句话大概率先出来
睡了2s... //如果没有加sleep(1)那块代码,这句话大概率先出来
from B: add one
from A: 1
from B: add one
from A: 2
from B: add one
from A: 3
...
...
from B: add one
from A: 99
from B: add one
from A: 100
线程A结束
线程B结束
简单分析
- 首先,我们在public类ThreadVisibilityTest先内置了一个队列,包装了ArrayList的add和size方法。这个list会被接下来的两个线程使用到。
- main方法里,我们有一个锁lock,然后是线程A的定义,用synchronized块包住,如果检查到list的载荷达到100,则监控结束,这个时候第28行的lock.notify()很重要,它用来将线程B从等待池拿到lock的竞争池。如果没有打到100,那么也要通知线程B,然后自己wait(),就是释放锁,自己阻塞在那里。
- 线程B也是synchronized块里面一个while循环,它判断list的载荷没有到达100,就往里加,每加一个就notify一下线程A(因为线程A调用wait()了以后就会进入等待池,如果没有人notify它,它就会一直呆在里面,即使线程B的synchronized块执行完锁释放了,呆在等待池里的线程A都不会去拿这个锁,导致一直等待。。。。)。然后就是wait(),把线程B自己阻塞,让锁竞争池中的线程A拿到锁去报数。
- 所以这样一环扣一环下去,线程A会先知道list的载荷已经达到100,notify一下线程B,自己就打印"线程A结束",然后break出while循环,永远地结束自己的synchronized块。
- 线程B这时候被notify了,就从第71行继续往下,到新一轮while循环,判断size,就直接走到else块break出去了。
小结
- synchronized可以保证块内的操作整体的原子性,而且访问的内存是线程间可见的,因此list在声明的时候不加volatile也可以。
- 大家可以看到线程A start()后,接了两个sleep,前面一个睡了1秒,后面一个睡了2秒,如果把第一个睡眠的代码去掉,那么很可能是先打印"睡了2s…",后打印"from A: 0", 因为start之后线程不会马上执行,而是进入就绪队列,操作系统可能会先执行掉主线程里的睡眠2秒那个代码,所以故意加了一秒钟,让线程A尽可能先被执行,当然也可以不加,不影响目的的实现。