模拟线程饥饿

思路和代码来自于网络,本文仅是个人记录性质,感谢原作者们。

思路

线程1、线程2来自于一个固定线程池newFixedThreadPool(2),然后分别都去执行任务A,任务A当中会向同一个线程池提交任务B返回Future,同时任务A使用Future.get()等待任务B的返回结果才能继续往下执行。
于是会发生以下现象:线程1、2都在执行任务A,都向线程池提交了任务B、且都等各自的任务B结束,线程池只有2个线程现在都在执行任务A所以两个任务B进入阻塞队列等待,而只有当线程1、2执行完任务A才会释放回池里执行任务B,所以陷入一种类似死锁的场景,线程饥饿。

/**
 * 模拟线程饥饿
 * */
public class ThreadStarvingTest {
	
	public static void main(String[] args) {
		ExecutorService executorService = Executors.newFixedThreadPool(2);
		
		executorService.submit(() -> {
			System.out.println(currentThreadName() + "接待客人");
			Future<String> cook = executorService.submit(() -> {
				System.out.println(currentThreadName() + "做回锅肉");
				return "回锅肉";
			});

			try {
				System.out.println(currentThreadName() + "上" + cook.get()); //Waits if necessary for the computation to complete, and thenretrieves its result.
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
		});

		executorService.submit(() -> {
			System.out.println(currentThreadName() + "接待客人");
			Future<String> cook = executorService.submit(() -> {
				System.out.println(currentThreadName() + "做鱼香肉丝");
				return "鱼香肉丝";
			});
		
			try {
				System.out.println(currentThreadName() + "上" + cook.get());
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (ExecutionException e) {
				e.printStackTrace();
			}
		});
		
	}
	
	public static String currentThreadName() {
		return Thread.currentThread().getName();
	}

}

返回结果:

pool-1-thread-1接待客人
pool-1-thread-2接待客人

如果把fixed线程池大小改成3,就会执行下去了:

pool-1-thread-1接待客人
pool-1-thread-2接待客人
pool-1-thread-3做回锅肉
pool-1-thread-1上回锅肉
pool-1-thread-3做鱼香肉丝
pool-1-thread-2上鱼香肉丝

线程的daemon属性

上面我们会发现两种情况里,jvm进程最后都没结束,以往印象里main()这类hello world测试程序主线程跑完程序就退出结束了,或者是在之前搞过写死循环或者阻塞(比如SocketServer那类程序),然后程序一直执行。所以这里我们jstack可以看到main线程跑完了,那么jvm进程没结束,是因为线程池的两个线程还继续等待执行任务。
我们只要把线程池里的线程都设置为daemon=true,那么就可以在main线程结束以后,也结束jvm进程了:

实现一个ThreadFactory

import java.util.concurrent.ThreadFactory;

public class MyThreadFactory implements ThreadFactory{

	@Override
	public Thread newThread(Runnable r) {
		Thread t = new Thread(r);
		t.setDaemon(true);
		return t;
	}

}

然后用ThreadPoolExecutor自定义线程池替换Executors.newFixedThreadPoll:

ExecutorService executorService = new ThreadPoolExecutor(3, 3, 0, //coreSize, maxSize, keepalivetime都是0
							TimeUnit.SECONDS,
							new LinkedBlockingQueue<Runnable>(), //任务队列也跟原来一样
							new MyThreadFactory(), //用自定义的ThreadFactory
							new AbortPolicy()); //拒绝策略也跟原来一样,用Abort

执行结果:

Thread-0接待客人
Thread-1接待客人
Thread-2做回锅肉
Thread-0上回锅肉
Thread-2做鱼香肉丝
Thread-1上鱼香肉丝
main主线程结束

主线程结束了,它启动的线程池里边的线程又都是daemon线程,所以会随着主线成结束而结束,所以jvm进程也就结束了。

posted on 2021-11-12 15:15  肥兔子爱豆畜子  阅读(52)  评论(0编辑  收藏  举报

导航