一.问题一引入。
- 在一个类中有一个容器list,线程1每次向容器中添加一个元素,一共添加10个,线程2开启一个循环,检测当list的大小等于5时,终止程序,那么首先线程2能否检测出list大小变化呢?程序如下:
public class ThreadCommunication {
//没有用volitile修饰,不能保证变量在多个线程中的可见性。
private static List<Integer> list=new ArrayList<>();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("当前线程大小为: " + list.size());
list.add(i);
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (; ; ) {
if (list.size() == 5) {
System.out.println("t2线程退出...");
throw new RuntimeException("收到通知,线程退出");
}
}
}
}, "t2");
t1.start();
t2.start();
}
}
运行结果应该是线程2无法终止的,不过,这要取决于你的JDK是client还是server端,如果是server,那么程序优化高,编译彻底,性能会有很大提升,那么性能高必然会牺牲一些东西,这个时候是线程2是检测不出来的,如果是client端,那么启动快,是一个轻量级的编译器,这个时候是线程2能够检测出来的,然后抛出异常退出。
- 那如果安装的是server端,要怎样线程2才能感知到呢?
答案就是volitile关键字,对于list变量使用volitile修饰,保证变量在多个线程中的可见性,这里不再演示。
二.问题二引入.
还是问题一中的代码,线程2在一个for循环中不断循环去检测,这样是比较浪费性能的,有没有一种更好的方式?答案就是wait/notify,代码如下所示:
public class ThreadCommunication {
private volatile static List<Integer> list=new ArrayList<>();
private static Object lock=new Object();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
for (int i = 0; i < 10; i++) {
System.out.println("当前线程大小为: " + list.size());
list.add(i);
if(list.size()==5){
lock.notify(); //唤醒线程1
try {
lock.wait(); //释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock){
if(list.size()!=5){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2线程退出...");
lock.notify(); //在抛出异常之前换醒线程2,使线程2继续执行
throw new RuntimeException("收到通知,线程退出");
}
}
}, "t2");
t2.start();
t1.start();
}
}
运行结果如下:可以看到线程1正常退出,线程2也可以全部执行完毕。
三.问题三引入:
使用wait/notify势必会用到锁,那有没有另一种方法不使用锁也可以达到上面的效果呢?答案就是并发包中的countDownLatch类.代码如下:
package 线程;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class ThreadCommunication {
private volatile static List<Integer> list=new ArrayList<>();
//构造参数为执行await之后代码的条件--conutDown的总数
//如果为2,代表执行两次countDown之后,才会执行await之后的代码
private static CountDownLatch countDownLatch=new CountDownLatch(1);
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("当前线程大小为: " + list.size());
list.add(i);
if(list.size()==5){
countDownLatch.countDown();
}
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
if(list.size()!=5){
try {
countDownLatch.await(); //一直阻塞,直到执行countDown才继续执行下面的代码
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2线程退出...");
throw new RuntimeException("收到通知,线程退出");
}
}, "t2");
t2.start();
t1.start();
}
}
执行结果如下所示:
总结:CountDownLatch比较经典的应用场景就是一个或多个线程需要达到某种条件之后才去继续执行。
这里再 给出一个具体的应用场景,就是我们想要测试redis的性能,往redis去存数据,用了50个线程,每个线程并发的去存储1w条数据,那么我们怎么知道这50个线程什么时候都执行完毕?看如下代码思路即可.