关于并发的简单面试题

题目 :两个线程,线程1向list中添加10个元素,线程2监测list中元素个数,当个数为5时,线程2提醒并结束自身。

可能想到的方案1:

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)死循环。

 

方案1修改:将integers改为成员变量并添加一个volatile关键字。

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修改的修改 :使用锁

一个看似正确的代码

代码逻辑:既然是线程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();
	}
}

 

要求提升:在线程2提示完成之后,线程1才能继续添加元素

使用双重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();
	}
}

未完待续...

你可能感兴趣的:(java,多线程,java)