2021-06-06

创建和使用线程的几种方法:
//方式1
Thread r  = new Thread(){
	@Qverride
	public void run(){
		log.debugg("线程执行内容")
	}
}
r.start();
		//方式2
      //工作内容
        Runnable r =  new Runnable(){
            @Override
            public void run() {
                System.out.println("工作内容");
            }
        };
        //创建线程
        Thread t2 = new Thread(r, "t2");
        //执行线程
        t2.start();
		//方式2.1
		//使用lambda表达式创建 
        Runnable t = () -> System.out.println("sdafa");
        Thread t2 = new Thread(t, "t2");
        t2.start();
      //方式三
        //FutureTask 接口继承了Runnable接口,和Future接口,Futre用于返回线程的执行结果;
        FutureTask<Integer> ftask = new FutureTask<>(() -> {
            System.out.println("111");
            //休眠1000毫秒后返回执行结果
            Thread.sleep(1000);
            return 100;
        });
        Thread t3 = new Thread(ftask, "t3");
        t3.start();
        //打印线程的执行结果
        System.out.println(ftask.get());
小结:
  • 方式一是把线程和任务合并在了一起,方式二是把线程和任务分开了
  • 用Runnable 更容易与线程池等高级api配合
  • 用Runnable 让任务类脱离了 Thread继承体系,更灵活
相关概念
栈与栈帧

JVM由堆,栈,方法区组成,每个线程启动的时候虚拟机都会分配一块栈内存

  • 每个栈由多个栈帧(frame)组成,对应每次方法调用时所占用的内存
  • 每个线程只有一个活动栈帧,对应正在执行的方法
线程的上下文切换(Thread Context Switch)

因为以下一些原因导致cpu不再执行当前线程,转而执行下一线程的代码

  • 线程的cpu时间片用完
  • 垃圾回收
  • 有更高优先级的线程许哟啊运行
  • 线程自己调用了sleep,yield,wait,join,park,synchronized,lock等方法
  • 状态包括包括程序计数器、虚拟机栈中每个栈帧的信息,如变量、操作数栈,返回地址等
  • Context Switch 频繁发生会影响性能
常见方法
方法名static功能说明注意
start()启动一个新的线程,在新的线程中运行run方法中的代码run方法只让线程进入就绪状态,里面代码不一定立刻运行(cpu的时间片还没有分配给它)。每个线程的start()方法只能调用一次,如果调用多次会出现 IllegalThreadStateException
run()新线程启动后会调用的方法如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为
join()等待线程运行结
join(long n)等待线程运行结束,最多等待 n 毫秒
getId()获取线程长整型的 idid 唯一
getName()获取线程名
setName(String)修改线程名
getPriority()获取线程优先级
setPriority(int)修改线程优先级java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率
getState()获取线程状态Java 中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
isInterrupted()判断是否被打断,不会清除 打断标记
isAlive()线程是否存活(还没有运行完毕)
interrupt()打断线程如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除 打断标记 ;如果打断的正在运行的线程,则会设置 打断标记 ;park 的线程被打断,也会设置 打断标记
interrupted()static判断当前线程是否被打断会清除 打断标记
currentThread()static获取当前正在执行的线程
sleep(long n)static让当前执行的线程休眠n毫秒,休眠时让出 cpu 的时间片给其它线程
yield()static提示线程调度器让出当前线程对CPU的使用主要是为了测试和调试

线程池

在这里插入图片描述

线程池状态
ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量

在这里插入图片描述
从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING
这些信息存储在一个原子变量 ctl 中,目的是将线程池状态与线程个数合二为一,这样就可以用一次 cas 原子操作进行赋值

// c 为旧值, ctlOf 返回结果为新值
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))));
// rs 为高 3 位代表线程池状态, wc 为低 29 位代表线程个数,ctl 是合并它们
private static int ctlOf(int rs, int wc) { return rs | wc; }
构造方法
public ThreadPoolExecutor(int corePoolSize,
								 int maximumPoolSize,
								 long keepAliveTime,
								 TimeUnit unit,
								 BlockingQueue<Runnable> workQueue,
								 ThreadFactory threadFactory,
								 RejectedExecutionHandler handler)
  • corePoolSize 核心线程数目 (最多保留的线程数)

  • maximumPoolSize 最大线程数目(核心线程数+备用线程数)

    • 备用线程:当线程池的核心线程和阻塞队列都满了,则会开启被备用线程,备用线程是有声明周期的,核心线程是没有生命周期的
  • keepAliveTime 生存时间 - 针对救急线程

  • unit 时间单位 - 针对救急线程

  • workQueue 阻塞队列

  • threadFactory 线程工厂 - 可以为线程创建时起个好名字

  • handler 拒绝策略 任务量超过最大线程数和阻塞队列长度之和的时候,启用拒绝策略

  • 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务。

  • 当线程数达到 corePoolSize 并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue 队列排队,直到有空闲的线程。

  • 如果队列选择了有界队列,那么任务超过了队列大小时,会创建 maximumPoolSize - corePoolSize 数目的线程来救急。

  • 如果线程到达 maximumPoolSize 仍然有新任务这时会执行拒绝策略。拒绝策略 jdk 提供了 4 种实现,其它著名框架也提供了实现

    • AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略
      北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
    • CallerRunsPolicy 让调用者运行任务
    • DiscardPolicy 放弃本次任务
    • DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
      Dubbo 的实现,在抛出 RejectedExecutionException 异常之前会记录日志,并 dump 线程栈信息,方
      便定位问题
    • Netty 的实现,是创建一个新线程来执行任务
    • ActiveMQ 的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
    • PinPoint 的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
  • 当高峰过去后,超过corePoolSize 的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTime 和 unit 来控制。

在这里插入图片描述
根据这个构造方法,JDK Executors 类中提供了众多工厂方法来创建各种用途的线程池

newFixedThreadPool:创建一个固定线程数的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
							 return new ThreadPoolExecutor(nThreads, nThreads,
							 0L, TimeUnit.MILLISECONDS,
							 new LinkedBlockingQueue<Runnable>());
}

特点:核心线程数 == 最大线程数(没有救急线程),因此也无需超时时间
适用于任务量已知,相对耗时的任务

newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
							 return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
							 60L, TimeUnit.SECONDS,
							 new SynchronousQueue<Runnable>());
}

特点:核心线程数是0,所有的线程都是救急线程

  • 最大线程数是Integer.MAX_VALUE
  • 所有线程超时时间都60秒
  • 队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交货)

整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲 1分钟后释放线
程。 适合任务数比较密集,但每个任务执行时间较短的情况

newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
							 return new FinalizableDelegatedExecutorService
							 (new ThreadPoolExecutor(1, 1,
							 0L, TimeUnit.MILLISECONDS,
							 new LinkedBlockingQueue<Runnable>()));
}

特点:希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程
也不会被释放。

  • 自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作
  • Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
    • FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
  • Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
    • 对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
任务提交
// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
 throws InterruptedException;
// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
 long timeout, TimeUnit unit)
 throws InterruptedException;
// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
 throws InterruptedException, ExecutionException;
 // 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
 long timeout, TimeUnit unit)
 throws InterruptedException, ExecutionException, TimeoutException;
关闭线程池
/*
线程池状态变为 SHUTDOWN
 - 不会接收新任务
 - 但已提交任务会执行完
 - 此方法不会阻塞调用线程的执行
*/
void shutdown();
/*
线程池状态变为 STOP
 - 不会接收新任务
 - 会将队列中的任务返回
 - 并用 interrupt 的方式中断正在执行的任务
*/
List<Runnable> shutdownNow();
// 不在 RUNNING 状态的线程池,此方法就返回 true
boolean isShutdown();
// 线程池状态是否是 TERMINATED
boolean isTerminated();
// 调用 shutdown 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
任务调度线程池
  • java.util.Timer:Timer是java1.5之后才有的。Timer的所有任务都是由同一个线程来调度的,所以所有的任务都是串行的,同一时间只有一个任务能执行,并且前面的任务出险异常会影响后面的任务执行。
  • ScheduledExecutorService:ScheduledExecutorService是ExceutorService的一个扩展接口。
    • 代码示例:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// 添加两个任务,希望它们都在 1s 后执行
executor.schedule(() -> {
 System.out.println("任务1,执行时间:" + new Date());
 try { Thread.sleep(2000); } catch (InterruptedException e) { }
}, 1000, TimeUnit.MILLISECONDS);
executor.schedule(() -> {
 System.out.println("任务2,执行时间:" + new Date());
}, 1000, TimeUnit.MILLISECONDS);

输出:
任务1,执行时间:Thu Jan 03 12:45:17 CST 2019 
任务2,执行时间:Thu Jan 03 12:45:17 CST 2019

//scheduleAtFixedRate:固定时间间隔执行
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleAtFixedRate(() -> {
 log.debug("running...");
}, 1, 1, TimeUnit.SECONDS);

//scheduleAtFixedRate 例子(任务执行时间超过了间隔时间):
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleAtFixedRate(() -> {
 log.debug("running...");
 sleep(2);
}, 1, 1, TimeUnit.SECONDS);

//scheduleWithFixedDelay 的间隔是 上一个任务结束 <-> 延时 <-> 下一个任务开始
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
log.debug("start...");
pool.scheduleWithFixedDelay(()-> {
 log.debug("running...");
 sleep(2);
}, 1, 1, TimeUnit.SECONDS);

整个线程池表现为:线程数固定,任务数多于线程数时,会放入无界队列排队。任务执行完毕,这些线程也不会被释放。用来执行延迟或反复执行的任务

正确处理执行任务异常

实现了ExecutorService接口的线程池,如果在执行任务时发成了异常是不会打印异常的,我们需要自己手动的去处理异常

  • 方法一:用try/catch把异常捕获并处理
  • 方法二:用 Future(带有返回结果的线程池)
    • 示例:
ExecutorService pool = Executors.newFixedThreadPool(1);
Future<Boolean> f = pool.submit(() -> {
 log.debug("task1");
 int i = 1 / 0;
 return true;
});
log.debug("result:{}", f.get());

tomcat线程池

  • LimitLatch 用来限流,可以控制最大连接个数,类似 J.U.C 中的 Semaphore 后面再讲
  • Acceptor 只负责【接收新的 socket 连接】
  • Poller 只负责监听 socket channel 是否有【可读的 I/O 事件】
  • 一旦可读,封装一个任务对象(socketProcessor),提交给 Executor 线程池处理
  • Executor 线程池中的工作线程最终负责【处理请求】

tomcat 线程池扩展了 ThreadPoolExecutor,行为稍有不同 如果总线程数达到 maximumPoolSize,这时不会立刻抛 RejectedExecutionException 异常,而是再次尝试将任务放入队列,如果还失败,才抛出 RejectedExecutionException 异常
execute方法中先try到父类中抛出的RejectedExecutionException异常,然后catch中尝试再次把任务加入队列,如果不成功则再往外抛出异常

tomcat线程池的配置
配置项默认值说明
acceptorThreadCount1acceptor 线程数量
pollerThreadCount1poller 线程数量
minSpareThreads10核心线程数,即 corePoolSize
maxThreads200最大线程数,即 maximumPoolSize
executor-Executor 名称,用来引用下面的 Executor
Executor 线程配置
配置项默认值说明
threadPriority5线程优先级
daemontrue是否守护线程
minSpareThreads25核心线程数,即 corePoolSize
maxThreads200最大线程数,即 maximumPoolSize
maxIdleTime6000线程生存时间,单位是毫秒,默认值即 1 分钟
maxIdleTimeInteger.MAX_VALUE队列长度
prestartminSpareThreadsfalse核心线程是否在服务器启动时启动

在这里插入图片描述

forkjoin线程池的
  1. 概念:
    Fork/Join 是 JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的 cpu 密集型运算
    所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计算,如归并排序、斐波那契数列、都可以用分治思想进行求解
    Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运算效率
    Fork/Join 默认会创建与 cpu 核心数大小相同的线程池
  2. 使用:
    提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值),例如下面定义了一个对 1~n 之间的整数求和的任务
//继承RecursiveTask:带有返回接口的任务类
//new RecursiveTask()会默认创建跟核心数相同的线程数
class AddTask1 extends RecursiveTask<Integer> {
 int n;
 public AddTask1(int n) {
 this.n = n;
 }
 @Override
 public String toString() {
 return "{" + n + '}';
 }
 @Override
 protected Integer compute() {
 // 如果 n 已经为 1,可以求得结果了
 if (n == 1) {
 log.debug("join() {}", n);
 return n;
 }
 
 // 将任务进行拆分(fork)
 AddTask1 t1 = new AddTask1(n - 1);
 t1.fork();//交给另一个线程来做
 log.debug("fork() {} + {}", n, t1);
 
 // 合并(join)结果
 int result = n + t1.join();
 log.debug("join() {} + {} = {}", n, t1, result);
 return result;
 }
}

改进:

class AddTask3 extends RecursiveTask<Integer> {
 
 int begin;
 int end;
 public AddTask3(int begin, int end) {
 this.begin = begin;
 this.end = end;
 }
 @Override
 public String toString() {
 return "{" + begin + "," + end + '}';
 }
 @Override
 protected Integer compute() {
 // 5, 5
 if (begin == end) {
 log.debug("join() {}", begin);
 return begin;
 }
 // 4, 5
  if (end - begin == 1) {
 log.debug("join() {} + {} = {}", begin, end, end + begin);
 return end + begin;
 }
 
 // 1 5
 int mid = (end + begin) / 2; // 3
 AddTask3 t1 = new AddTask3(begin, mid); // 1,3
 t1.fork();
 AddTask3 t2 = new AddTask3(mid + 1, end); // 4,5
 t2.fork();
 log.debug("fork() {} + {} = ?", t1, t2);
 int result = t1.join() + t2.join();
 log.debug("join() {} + {} = {}", t1, t2, result);
 return result;
 }
}

AQS原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wenriyan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值