线程池的使用

线程池的作用:
线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排 队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池 中有等待的工作线程,就可以开始运行了;否则进入等待队列。

为什么要用线程池:
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>volatile

    java线程变量加载的大致流程是,将主内存的变量加载到工作内存进行处理,处理完毕后写会主内存;
    线程读volatile变量的过程,有一些类似加锁的机制,因此可以做到线程可见性:
    1对volatile变量执行读操作时,会在读操作前加入一条load屏障指令,从主内存中读取volatile变量的最新值到线程的工作内存中,等于这种变量每次使用都会从工作内存中按到最新的。
    2对volatile变量执行写操作时,会在写操作后加入一条store屏障指令,避免其他线程操作,从工作内存中读取volatile变量的副本,工作内存中就是最新的了
    深入来说:通过加入内存屏障和禁止重排序优化来实现的
    通俗的讲:volatile变量在每次被线程访问时,都强迫从主内存中重读该变量的值,而当该变量发生变化时,又会强迫线程将最新的值刷新到主内存。这样任何时刻,不同的线程总能看到该变量的最新值。