public class Demo1 {
public static void main(String[] args) {
List integers = new ArrayList<>(10);
new Thread(() -> {
for (int i=0;i<10;i++) {
integers.add(i);
System.out.println("thread1 -> current integers size = " + integers.size());
}
}).start();
new Thread(()->{
while (true){
if (5 == integers.size()){
break;
}
}
System.out.println("thread2 -> integers size equals to 5");
}).start();
}
}
但:该方案是不正确的
原因:在上面的代码中两个线程中的list其实不是同一个list(为什么?明明是使用相同变量integers啊)。这是由于每个线程在创建时会在各自线程内部内存空间里生成一个integers集合当前状态的副本。因此线程1对integers进行操作时,线程2不可见,线程2中永远时integers最初状态,所以线程2会一个进行whiel(true)死循环。
public class Demo1 {
private volatile static List integers = new ArrayList<>(10);
public static void main(String[] args) throws Exception{
new Thread(() -> {
for (int i = 0; i < 10; i++) {
integers.add(i);
System.out.println("thread1 -> current integers size = " + integers.size());
}
}).start();
new Thread(() -> {
while (true) {
if (5 == integers.size()) {
break;
}
}
System.out.println("thread2 -> integers size equals to 5");
}).start();
}
}
这时线程2就可以监测到integers元素的变化,这是由于volatile关键字的可见性约束,线程1对integers的修改会立即反应到主存中的integers中,并且线程2需要使用integers变量时会强制要求线程2去访问主存中的integers指向的对象,所以此时线程2就可以完成监测integers中的元素变化。
这里需要介绍以线volatile关键字的意义,volatile的一个作用就是让每个线程在访问被volatile修饰的变量被修改时会立即回写到主存中,其他线程在读取被volatile修饰的变量时必须去主存中重新读取,这样就保证了其他都线程(线程2)访问到的integers变量是主存中最新的。
所以,修改后的代码也不是完美无瑕。
原因是上面说的线程2访问到的只能是主存中最新的,如果在线程2访问到主存中最新的integers之后,在对integers变量进行判断之前,线程1已经再次完成对integers添加一个元素,而线程2已经完成了最新主存数据的访问,只会继续执行线程2的代码,不会再次访问主存中的数据,这就导致会出现线程2在integers已经是6个元素的时候才会发出integers元素为5个的提示。
一个看似正确的代码
代码逻辑:既然是线程1修改integers,那么线程1一定可以知道最新的integers状态,线程2在integers元素数量不为5时 使用lock锁等待,线程1在执行过程中判断integers元素的个数,为5则唤醒以lock为锁的线程。
public class Demo1_2 {
public static void main(String[] args) throws Exception {
List integers = new ArrayList<>(10);
final Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
integers.add(i);
if (5 == integers.size()) {
lock.notify();
}
System.out.println("thread1 -> current integers size = " + integers.size());
}
}
}).start();
new Thread(() -> {
if (5 != integers.size()) {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("thread2 -> integers size equals to 5");
}).start();
}
}
代码不正确的原因时在线程1中以lock为锁执行的代码是一个for循环,所以尽管在integers元素为5的时候唤醒了,但是由于线程1在for循环结束之前一直占有lock锁,导致线程2无法获得锁,需要等到线程1完全结束后,线程2才能获得lock锁再提示。修改方法很简单,把线程1的synchronized(lock)放进if判断中进行即可。
注意:此处还可能出现线程1已经将integers添加到5个元素进行lock.notify(),但是线程2还没运行,这就导致线程2执行时会一直wait无人唤醒,程序无法正常结束。这个问题的修改是在线程1中进行睡眠,强制让线程2先执行。
使用CountDownLatch
public class Demo1_4 {
public static void main(String[] args) throws Exception {
List integers = new ArrayList<>(10);
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
integers.add(i);
if (5 == integers.size()) {
latch.countDown();
}
System.out.println("thread1 -> current integers size = " + integers.size());
}
}).start();
new Thread(() -> {
if (5 != integers.size()) {
try {
latch.await();
System.out.println("thread2 -> integers size equals to 5");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
使用双重wait和notify,在线程1中监测到integers元素数量为5时唤醒线程2,自身进行lock.wait()释放锁,让线程2有机会获得锁,在线程2执行完成后,线程2唤醒线程1,实现在线程2提示完成之后,线程1继续执行的功能。
public class Demo1_3 {
public static void main(String[] args) throws Exception {
List integers = new ArrayList<>(10);
final Object lock = new Object();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
for (int i = 0; i < 10; i++) {
integers.add(i);
if (5 == integers.size()) {
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("thread1 -> current integers size = " + integers.size());
}
}
}).start();
new Thread(() -> {
if (5 != integers.size()) {
synchronized (lock) {
try {
lock.wait();
System.out.println("thread2 -> integers size equals to 5");
lock.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
未完待续...