题目:实现一个容器,提供两个方法,add,size。写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束。
程序一,分析下面程序能否实现
List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
MyContainer1 c = new MyContainer1();
new Thread(() -> { //线程一
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add" + i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
}
}, " t1").start();
new Thread(() -> { //线程二
while (true) {
if (c.size() == 5) {
break;
}
}
System.out.println("t2线程结束");
}, "t2").start();
}
运行结果:线程1运行完成,正常打印。线程2一直在运行,未结束。
分析:lists对于线程2不可见,所以不能实时的访问到lists的size。
程序二 加volatile关键字
//添加volatile,使t2能够得到通知
volatile List lists = new ArrayList();
public void add(Object o){
lists.add(o);
}
public int size(){
return lists.size();
}
public static void main(String[] args) {
MyContainer2 c = new MyContainer2();
new Thread(()->{ //线程一
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}," t1").start();
new Thread(()->{ //线程二
while (true) {
if (c.size() == 5) {
break;
}
}
System.out.println("t2线程结束");
}, "t2").start();
}
运行结果:正常
分析:由于volatile关键字保证了lists对于线程2可见,所以当size发生变化后线程2能获取到lists的size值。
不足:线程2的死循环非常浪费cpu资源。
程序三 使用wait和notify实现
//添加volatile,使t2能够得到通知
volatile List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
MyContainer4 c = new MyContainer4();
Object lock = new Object();
new Thread(() -> { //线程一
synchronized (lock) {
System.out.println("t2启动");
if (c.size() != 5) {
try {
lock.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("t2结束");
lock.notify();
}
}, " t2").start();
new Thread(() -> { //线程二
System.out.println("t1启动");
synchronized (lock) {
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
lock.notify();
// 释放锁,让t2得以执行
try {
lock.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}, "t1").start();
}
这里使用wait和notify做到,wait会释放锁,而notify不会释放锁。需要注意的是运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以。
程序四 使用Latch(门闩)替代wait notify来进行通知
//添加volatile,使t2能够得到通知
volatile List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
MyContainer5 c = new MyContainer5();
Object lock = new Object();
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> { //线程一
System.out.println("t2启动");
if (c.size() != 5) {
try {
latch.await();
// 也可以指定等待时间]
//latch.await(5000, TimeUnit.MICROSECONDS);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("t2结束");
}
}, " t2").start();
new Thread(() -> { //线程二
System.out.println("t1启动");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
// 打开门阀,让t2得以执行
latch.countDown();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
}
}, "t1").start();
}
使用Latch(门闩)替代wait notify来进行通知 好处是通信方式简单,同时也可以指定等待时间 使用await和countdown方法替代wait和notify。
CountDownLatch不涉及锁定,当count的值为零时当前线程继续运行。 当不涉及同步,只是涉及线程通信的时候,用synchronized + wait/notify就显得太重了。这时应该考虑countdownlatch/cyclicbarrier/semaphore
读下面文章进行深入了解:
Java并发编程:CountDownLatch、CyclicBarrier和 Semaphore