共计 3445 个字符,预计需要花费 9 分钟才能阅读完成。
因为本周大部分工夫都在写原型,没有遇到什么问题,再加上之前会议中也屡次提到了线程,而我自己对线程没有什么了解于是便有了以下文章。
为什么应用多线程
在咱们开发零碎过程中,常常会解决一些费时间的工作(如:向数据库中插入大量数据),这个时候就就须要应用多线程。
Springboot 中是否对多线程办法进行了封装
是,Spring 中可间接由 @Async 实现多线程操作
如何控制线程运行中的各项参数
通过配置线程池。
线程池 ThreadPoolExecutor 执行规定如下
而后咱们来认为结构一个线程池来试一下:
@Configuration
@EnableAsync
public class ThreadPoolConfig implements AsyncConfigurer {
/**
* 外围线程池大小
*/
private static final int CORE_POOL_SIZE = 3;
/**
* 最大可创立的线程数
*/
private static final int MAX_POOL_SIZE = 10;
/**
* 队列最大长度
*/
private static final int QUEUE_CAPACITY = 10;
/**
* 线程池保护线程所容许的闲暇工夫
*/
private static final int KEEP_ALIVE_SECONDS = 300;
/**
* 异步执行办法线程池
*
* @return
*/
@Override
@Bean
public Executor getAsyncExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
executor.setThreadNamePrefix("LiMingTest");
// 线程池对回绝工作 (无线程可用) 的解决策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
ThreadPoolExecutor 是 JDK 中的线程池实现,这个类实现了一个线程池须要的各个办法,它提供了工作提交、线程治理、监控等办法。
corePoolSize:外围线程数
线程池保护的最小线程数量,默认状况下外围线程创立后不会被回收(留神:设置 allowCoreThreadTimeout=true 后,闲暇的外围线程超过存活工夫也会被回收)。
大于外围线程数的线程,在闲暇工夫超过 keepAliveTime 后会被回收。
maximumPoolSize:最大线程数
线程池容许创立的最大线程数量。
当增加一个工作时,外围线程数已满,线程池还没达到最大线程数,并且没有闲暇线程,工作队列已满的状况下,创立一个新线程,而后从工作队列的头部取出一个工作交由新线程来解决,而将刚提交的工作放入工作队列尾部。
keepAliveTime:闲暇线程存活工夫
当一个可被回收的线程的闲暇工夫大于 keepAliveTime,就会被回收。
被回收的线程:
设置 allowCoreThreadTimeout=true 的外围线程。大于外围线程数的线程(非核心线程)。
workQueue:工作队列
新工作被提交后,如果外围线程数已满则会先增加到工作队列,任务调度时再从队列中取出工作。工作队列实现了 BlockingQueue 接口。
handler:回绝策略
当线程池线程数已满,并且工作队列达到限度,新提交的工作应用回绝策略解决。能够自定义回绝策略,回绝策略须要实现 RejectedExecutionHandler 接口。
JDK 默认的回绝策略有四种:
AbortPolicy:抛弃工作并抛出 RejectedExecutionException 异样。DiscardPolicy:抛弃工作,然而不抛出异样。可能导致无奈发现零碎的异样状态。DiscardOldestPolicy:抛弃队列最后面的工作,而后从新提交被回绝的工作。CallerRunsPolicy:由调用线程解决该工作。
咱们在非测试文件中间接应用 new Thread 创立新线程时编译器会收回正告:
不要显式创立线程,请应用线程池。阐明:应用线程池的益处是缩小在创立和销毁线程上所花的工夫以及系统资源的开销,解决资源有余的问题。如果不应用线程池,有可能造成零碎创立大量同类线程而导致耗费完内存或者“适度切换”的问题
public class TestServiceImpl implements TestService {private final static Logger logger = LoggerFactory.getLogger(TestServiceImpl.class);
@Override
public void task(int i) {logger.info("工作:"+i);
}
}
@Autowired
TestService testService;
@Test
public void test() {for (int i = 0; i < 50; i++) {testService.task(i);
}
咱们能够看到所有执行失常;
之后我有对线程进行了一些测试:
class TestServiceImplTest {
@Test
public void test() {Thread add = new AddThread();
Thread dec = new DecThread();
add.start();
dec.start();
add.join();
dec.join();
System.out.println(Counter.count);
}
static class Counter {public static int count = 0;}
class AddThread extends Thread {public void run() {for (int i=0; i<10000; i++) {Counter.count += 1;}
}
}
class DecThread extends Thread {public void run() {for (int i=0; i<10000; i++) {Counter.count -= 1;}
}
}
一个自增线程,一个自减线程,对 0 进行同样次数的操作,理当后果依然为零,然而执行后果却每次都不同。
通过搜寻之后发现 对变量进行读取和写入时,后果要正确,必须保障是原子操作。原子操作是指不能被中断的一个或一系列操作
。
例如,对于语句:n +=1; 看似只有一行语句却包含了 3 条指令:
读取 n, n+1, 存储 n;
比方有以下两个过程同时对 10 进行加 1 操作
这阐明多线程模型下,要保障逻辑正确,对共享变量进行读写时,必须保障一组指令以原子形式执行:即某一个线程执行时,其余线程必须期待。
static class Counter {public static final Object lock = new Object();// 每个线程都需取得锁能力执行
public static int count = 0;
}
class AddThread extends Thread {public void run() {for (int i=0; i<10000; i++) {synchronized(Counter.lock) { static class Counter {public static final Object lock = new Object();
public static int count = 0;
}
class DecThread extends Thread {public void run() {for (int i=0; i<10000; i++) {synchronized(Counter.lock) {Counter.count -= 1;}
}
}
}
值得注意的是每个类能够设置多个锁,如果线程获取的不是同一个锁则无奈起到上述性能;
springBoot 中也定义了很多类型的锁,在此就不一一阐明了,咱们目前能做到的就是留神我的项目中的异步操作,察看操作所应用的线程,做到在当前我的项目中遇到此类问题时能及时发现问题,解决问题。