要求:
- 实现一个容器,提供两个方法,add,size
- 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
1.方案一:volatile
public class MyContainer<T> {
// 复用 ArrayList
// 注:这里使用 volatile 保证两个线程都能感知到修改
volatile List<T> list = new ArrayList<>();
// 使用 List#add
void add(T t){
list.add(t);
}
// 使用 List#size
int size(){
return list.size();
}
public static void main(String[] args) {
MyContainer<Integer> myContainer = new MyContainer<>();
// thead1 负责向容器add
new Thread(()->{
for(int i=1;i<=10;i++){
System.out.println(i);
myContainer.add(i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// thread2 监听容器变化
new Thread(()->{
while (true){
if(myContainer.size() == 5){
System.out.println("!!!");
break;
}
}
}).start();
}
}
给 lists 添加 volatile之后,t2 能够接到通知,但是,t2 线程的死循环很浪费cpu,如果不用死循环,该怎么做呢?
2.方案二:wait/notify
这里使用 wait 和 notify 做到,wait 会释放锁,而 notify 不会释放锁。
注意:
- 运用这种方法,必须要保证 t2 先执行,也就是首先让 t2 监听才可以
- notify 之后,t1 必须释放锁(wait),t2 退出后,也必须 notify,通知 t1 继续执行。整个通信过程比较繁琐
public class MyContainer02<T> {
volatile List<T> list = new ArrayList<>();
void add(T t){
this.list.add(t);
}
int size(){
return list.size();
}
public static void main(String[] args) {
MyContainer02<Integer> container02 = new MyContainer02<>();
// thread2(监听线程)先启动
new Thread(()->{
try {
synchronized (container02){
if (container02.size() != 5){
// 释放锁,休眠
container02.wait();
}
System.out.println("!!!!!!");
container02.notify();
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// thread1(add线程)再启动
new Thread(()->{
synchronized (container02){
// 向容器 add
for(int i=0;i<10;i++){
container02.list.add(i);
System.out.println(i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 待放了 5 个时,
if(i == 4){
// notify 监听线程
container02.notify();
try {
// 自己进入休眠
container02.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
}
3.方案三:countDownLatch
使用 Latch(门闩)替代 wait notify 来进行通知,好处是通信方式简单,同时也可以指定等待时间。使用 await 和 countdown 方法替代 wait 和 notify,CountDownLatch 不涉及锁定,当 count 的值为零时当前线程继续运行。
PS:当不涉及同步,只是涉及线程通信的时候,用 synchronized + wait/notify 就显得太重了;这时应该考虑countdownlatch/cyclicbarrier/semaphore
public class MyContainer03<T> {
volatile List<T> list = new ArrayList<>();
void add(T t){
this.list.add(t);
}
int size(){
return list.size();
}
public static void main(String[] args) {
MyContainer03<Integer> container03 = new MyContainer03<>();
// 初始化 CountDownLatch 为 1
CountDownLatch countDownLatch = new CountDownLatch(1);
// thread1(监听线程)
new Thread(()->{
try {
// await() 阻塞等待 countdownLatch 减为 0
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("!!!");
}).start();
// thread2(add线程)
new Thread(()->{
for(int i=0;i<10;i++){
container03.add(i);
System.out.println(i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 当放了 5 个时
if(container03.size() == 5){
// 将 countDownLatch--(为0,此时t1被唤醒)
countDownLatch.countDown();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}