线程池的使用
线程池的作用:
线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排 队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池 中有等待的工作线程,就可以开始运行了;否则进入等待队列。
为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
new ThreadPoolTaskExecutor 创建一个自定义任务线程池,参数自定,比较常用项目启动就配置好的线程池示例
@Bean(name = “taskExecutor”)
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setMaxPoolSize(10);
taskExecutor.setCorePoolSize(5);
taskExecutor.setQueueCapacity(2000);
taskExecutor.setThreadNamePrefix(“emcs-executor-task-thread”);
taskExecutor.initialize();
return taskExecutor;
}线程的创建方式
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和Future创建线程
4)使用线程池例如用Executor框架
线程池的使用示例,不需要线程返回值的时候callable接口也可以换成Runnable,
import org.junit.Test;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;/**
• @author
• @date 2020-10-15.
*/
public class ThreadPoolTest{
static ExecutorService executorService = Executors.newFixedThreadPool(5);
/**• 计算价格的任务,创建callable线程
• @author hadoop
•
*/
private class QuoteTask implements Callable {
public final double price;
public final int num;public QuoteTask(double price, int num) {
this.price = price;
this.num = num;
}
@Override
public BigDecimal call() throws Exception {
Random r = new Random();
long time = (r.nextInt(10) + 1) * 1000;
Thread.sleep(time);
BigDecimal d = BigDecimal.valueOf(price * num).setScale(2);
System.out.println("耗时:" + time / 1000 + "s,单价是:" + price + ",人数是:"
+ num + ",总额是:" + d);
return d;
}}
/**
• 执行单个线程任务
• @throws Exception
*/
@Test
public void test1() throws Exception{
Callable callable = new QuoteTask(200,1);
Future future = executorService.submit(callable);
System.out.println(new Date()+ “::”+future.get());
executorService.shutdown();
}/**
• 执行多个线程任务
• @throws Exception
*/
@Test
public void test2() throws Exception{
List quoteTaskList = new ArrayList<>();
for(int i=1 ;i<=10 ;i++){
QuoteTask quoteTask = new QuoteTask(20,i);
quoteTaskList.add(quoteTask);
}
List<Future> futures = executorService.invokeAll(quoteTaskList,10, TimeUnit.SECONDS);
// 报价合计集合
List totalPriceList = new ArrayList();
Iterator taskIter = quoteTaskList.iterator();
for (Future future : futures) {
QuoteTask task = taskIter.next();
try {
totalPriceList.add(future.get());
} catch (ExecutionException e) {
totalPriceList.add(BigDecimal.valueOf(-1));
System.out.println(“任务执行异常,单价是”+task.price+",人数是:"+task.num);
} catch (CancellationException e) {
totalPriceList.add(BigDecimal.ZERO);
System.out.println(“任务超时,取消计算,单价是”+task.price+",人数是:"+task.num);
}
}
for (BigDecimal bigDecimal : totalPriceList) {
System.out.println(bigDecimal);
}
executorService.shutdown();
}}线程的可见性
什么是线程间的可见性?
一个线程对共享变量值的修改,能够及时的被其他线程看到。
什么是共享变量?
如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
什么是java内存模型?(Java Memory Model,简称JMM)
JMM描述了java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节。
规则1:
1>所有的变量都存储在主内存中
2>每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)
规则2:
1>线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写
2>不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量的传递需要通过主内存来完成
共享变量可见性实现的原理:
线程1对共享变量的修改要想被线程2及时看到,必须经过如下2个步骤:
1>把工作内存1中更新过的共享变量刷新到主内存中
2>将主内存中最新的共享变量的值更新到工作内存2中
java语言层面支持的可见性实现方式有以下两种:
1>synchronized
2>volatilejava线程变量加载的大致流程是,将主内存的变量加载到工作内存进行处理,处理完毕后写会主内存;
线程读volatile变量的过程,有一些类似加锁的机制,因此可以做到线程可见性:
1对volatile变量执行读操作时,会在读操作前加入一条load屏障指令,从主内存中读取volatile变量的最新值到线程的工作内存中,等于这种变量每次使用都会从工作内存中按到最新的。
2对volatile变量执行写操作时,会在写操作后加入一条store屏障指令,避免其他线程操作,从工作内存中读取volatile变量的副本,工作内存中就是最新的了
深入来说:通过加入内存屏障和禁止重排序优化来实现的
通俗的讲:volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存。这样任何时刻,不同的线程总能看到该变量的最新值。
















