我们通常可以new Thread(()->{线程执行的任务}).start()这种形式开启一个线程,当run()方法运行结束后,线程对象会被gc释放
在真实的环境中,可能需要很多线程来支撑整个应用,当线程数量非常多的时候,反而会耗尽cpu的资源,如果不对线程进行控制与管理,反而会影响程序的性能,线程开销主要包括:创建启动线程的开销;线程销毁的开销;线程调度的开销,即上下文切换时的开销,而且线程的数量会受限cpu处理器的数量的影响
线程池则是有效使用线程的一种常用方式,线程池内部可以预先创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交给线程池,线程池将这些任务缓存在工作队列中,线程池中的工作线程不断的从队列中取出任务并执行
查看executors工具类中newCachedThreadPool()、new SingleThreadExecutor()、newFixedThreadPool()源码
newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
使用的直接提交的任务队列,这个队列不会实际的存储任务,而是在每一次提交任务时创建一个线程,适合执行大量耗时短并且提交频繁的任务
new SingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
在任意时刻只有一个线程在执行任务,消费者生产者模式中就可以使用这个线程池
newFixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
不难看出这些返回线程池的方法都使用了ThreadPoolExecutor线程池,这些方法都是ThreadPoolExecutor线程池的封装
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
其构造方法中参数的含义为
说明:
workqueue工作队列是指提交未执行的任务队列,它是Blockqueue接口的对象,仅用于存储runnable任务,根据队列功能分类,在ThreadPoolExecutor构造方法中可以使用以下几种阻塞队列
当提交给线程池的任务数量超过了实际承载能力时,如何处理?及线程池中的线程已经用完了,等待队列也满了,无法为新的任务服务就可以通过拒绝策略来处理这个问题,jdk内置了四种拒绝策略:
executors工具类提供的静态方法返回的线程池的默认拒绝策略是abortpolicy即排除异常
如果内置的策略无法满足,还可以自定义,即扩展RejectedExecutionHandler接口
代码如下
package com.cxf.test;
import java.util.concurrent.*;
public class test1 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1, 1, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1),
Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("满了,俺feifei处理不了");
}
}
);
for (int i = 0; i <3 ; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
线程池中的线程从哪来的?
答案是threadfactory
threadfactory是一个接口,只有一个用来创建线程的方法
同样的该方法我们也
线程池对系统性能是有一定影响的,过大或者过小都会无法发挥最优的系统性能,线程池大小不需要非常精确,只要避免极大或者极小的情况即可,一般来说,线程池的大小需要考虑cpu数量,内存大小等因素可以来自定义,这里就不再演示
如果在线程池中执行的任务a在执行过程中又向线程池中提交了任务b,任务b添加到了线程池的等待队列中,如果任务a的结束需要等待任务b的执行结果就有可能会出现这种情况,线程池中的所有工作线程都处于等待任务处理结果,而这些任务在阻塞队列中等待执行,线程池中没有可以对阻塞队列中的任务进行处理的线程,这种等待就会一直持续下去,从而造成死锁
适合给线程池提交相互独立的任务,而不是相互依赖的任务,对于相互依赖的任务,可以考虑提交给不同的线程池
从面向对象设计的角度出发介绍几种保障线程安全的设计技术,这些技术可以使我们在不必借助锁的情况下保障线程安全,避免锁可能导致的问题及开销
java运行时空间可以分为栈区,堆区与方法区
栈空间为线程的执行准备一段固定大小的存储空间,每个线程都有独立的线程栈空间,创建线程时,就为线程分配栈空间,在线程中每调用一个方法就给方法分配一个栈帧,栈帧里的局部变量表用来存储局部变量,返回值等私有数据,基本变量存储在栈空间中,引用类型的变量值也是存储在栈空间中,引用的对象存储在堆中,由于线程间是相互独立的,一个线程不能访问另一个线程的栈空间,因此线程对局部变量以及只有通过当前线程的局部变量才能访问的对象进行的操作都是线程安全的
而堆中存储的对象,是多个线程可以共享的,如果此时多线程并发操作,共享对象,且有数据的修改就有可能出现线程不安全的情况
非堆空间,即jdk8后,称为元空间,存储类元数据,方法信息,jit代码缓存等也是多个线程共享的,因此也有可能出现线程不安全现象
对象就是数据及对数据操作的封装,对象所包含的数据称为对象的状态,实例变量与静态变量称为状态变量
如果一个类的同一个实例被多个线程共享,不会使这些线程存在共享状态,那么就称这个类为无状态对象,反之,则称为有状态对象,实际上无状态对象就是不包含任何实例变量,也不包含任何静态变量的对象
线程安全问题的前提是多个线程存在共享的数据,实现线程安全的一种方法就是避免在多个线程之间共享数据,使用无状态对象就是这种方法
不可变对象是指一经创建它的状态就保持不变的对象,不可变对象具有固有的线程安全性,当不可变对象现实实体发生改变时会创建一个新的不可变对象,就如string字符串对象,一个不可变对象需要满足以下条件:
不可变对象的应用场景:
1.被建模对象的状态变化不频繁
我们可以不共享非线程安全的对象,对于非线程安全的对象,每个线程都创建一个该对象的实例,各个线程访问各自创建的实例,一个线程不能访问另一个线程创建的实例,这种各个线程创建各自的实例,一个实例只能被一个线程访问的对象就称为线程特有对象,线程特有对象即保障了对非线程安全对象的访问的线程安全,又避免了锁的开销,线程特有对象也具有固有的线程安全性
threadloacl 类相当于线程访问其特有对象的代理,即各个线程通过threadlocal对象可以创建并访问各自的线程特有对象,一个线程可以使用不同的threadloacl实例来创建访问不同的线程特有对象
threalocal实例为每个访问它的对象都关联了一个该线程特有的对象,threadloacl实例都有当前线程与特有实例之间的一个关联
装饰器模式可以用来实现线程安全,基本思想是为非线程安全的对象创建一个相应的线程安全的外包装对象,客户端代码不直接访问非线程安全的对象而是访问它的外包装对象,外包装对象与非线程安全的对象有相同的接口,即外包装对象的使用方式与线程安全的对象的使用方式完全相同,而外包装对象通常会借助锁,以线程安全的方式调用相应的非线程安全对象的方法
在java.util.collections工具类中提供了一组synchronizedxxx(xxx)可以把不是线程安全的xxx集合转换为线程安全的集合,他就是采用了这种装饰器模式,这个方法的返回值就是指定集合的外包装对象,这类集合又被称为同步集合
使用装饰器模式的好处就是实现关注点分离,在这种设计模式中,实现同一组功能的两个版本;非线程安全的对象和线程安全的对象,对于非线程安全的在设计时只需要关注实现的功能,线程安全的只需要关注线程的安全性