素材网站的图可以做海报吗,自己弄个网站,在线字体设计网站,潍坊汇聚网站ThreadLocal
1.ThreadLocal是什么
ThreadLocal类让每一个线程都拥有了自己的本地变量#xff0c;这意味着每个线程都可以独立地、安全地操作这些变量#xff0c;而不会影响其他线程。
ThreadLocal的常用API get()#xff1a;获取当前线程中与ThreadLocal对象关联的变量副…ThreadLocal
1.ThreadLocal是什么
ThreadLocal类让每一个线程都拥有了自己的本地变量这意味着每个线程都可以独立地、安全地操作这些变量而不会影响其他线程。
ThreadLocal的常用API get()获取当前线程中与ThreadLocal对象关联的变量副本。 set(T value)将指定的值设置为当前线程中与ThreadLocal对象关联的变量副本。 remove()删除当前线程中与ThreadLocal对象关联的变量副本。这样可以避免内存泄漏问题。注意remove()方法只会删除当前线程中的变量副本不会影响其他线程中的副本。 initialValue()当调用get()或set()方法时如果当前线程没有与ThreadLocal对象关联的变量副本则会调用initialValue()方法创建一个新的变量副本并与当前线程关联。默认情况下initialValue()方法返回null可以通过继承ThreadLocal类并重写initialValue()方法来自定义初始化值。
2.ThreadLocal原理了解吗
从 Thread 类源代码入手。
public class Thread implements Runnable {//......//与此线程有关的ThreadLocal值。由ThreadLocal类维护ThreadLocal.ThreadLocalMap threadLocals null;//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护ThreadLocal.ThreadLocalMap inheritableThreadLocals null;//......
}从上面 Thread 类中可以看出 Thread 类中有一个 threadLocals 和一个 inheritableThreadLocals 变量它们都是 ThreadLocalMap 类型的变量。默认情况下这两个变量都是 null只有当前线程调用 ThreadLocal 类的 set 或 get 方法时才创建它们实际上调用这两个方法的时候我们调用的是ThreadLocalMap 类对应的 get、set 方法。
ThreadLocal 类的 set 方法
public void set(T value) {//获取当前请求的线程Thread t Thread.currentThread();//取出 Thread 类内部的 threadLocals 变量(哈希表结构)ThreadLocalMap map getMap(t);if (map ! null)// 将需要存储的值放入到这个哈希表中map.set(this, value);elsecreateMap(t, value);
}
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}最终的变量是放在了当前线程的 ThreadLocalMap 中并不是存在 ThreadLocal 上ThreadLocal 可以理解为只是 ThreadLocalMap 的封装传递了变量值。 ThrealLocal 类中可以通过Thread.currentThread() 获取到当前线程对象后直接通过 getMap(Thread t) 可以访问到该线程的ThreadLocalMap对象。
每个 Thread中 都具备一个 ThreadLocalMap而 ThreadLocalMap 可以存储以 ThreadLocal 为 key Object 对象为 value 的键值对。
ThreadLocalMap(ThreadLocal? firstKey, Object firstValue) {//......
}比如我们在同一个线程中声明了两个 ThreadLocal 对象的话 Thread内部都是使用仅有的那个ThreadLocalMap 存放数据的ThreadLocalMap的 key 就是 ThreadLocal对象value 就是 ThreadLocal 对象调用set方法设置的值。ThreadLocal 数据结构如下图所示。 ThreadLocalMap 是 ThreadLocal 的静态内部类。 3.ThreadLocal 内存泄露问题是怎么导致的
内存泄漏和内存溢出的区别是什么 内存泄漏指的是程序中分配的内存在不再需要时没有被正确释放或回收的情况。 内存溢出指的是程序试图分配超过其可用内存的内存空间的情况。
ThreadLocal 对象和 ThreadLocalMap 中使用的 key 是弱引用而 value 是强引用。所以如果 ThreadLocal 没有被外部强引用的情况下在垃圾回收的时候key 会被清理掉。但是如果Thread 对象一直在被使用比如在线程池中被重复使用那么从Thread 对象到 value 的引用链就一直在导致 value 不会被清理掉。这样一来ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话value 永远无法被 GC 回收这个时候就可能会产生内存泄露。
ThreadLocalMap 实现中已经考虑了这种情况在调用 set、get、remove 方法的时候会清理掉 key 为 null 的 Entry。因此使用完 ThreadLocal 方法后最好手动调用一下 remove 方法就可以在下一次 GC 的时候把 key 为 null 的 Entry 清理掉。 线程池
1.什么是线程池?
线程池就是管理一系列线程的资源池。当有任务要处理时直接从线程池中获取线程来处理处理完之后线程并不会立即被销毁而是等待下一个任务。
2.为什么要用线程池
池化技术想必大家已经屡见不鲜了线程池、数据库连接池、HTTP 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗提高对资源的利用率。 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 提高响应速度。当任务到达时任务可以不需要等到线程创建就能立即执行。 提高线程的可管理性。线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进行统一的分配调优和监控。
3.如何创建线程池 方式一通过ThreadPoolExecutor构造函数来创建推荐
代码示例
ExecutorService pools new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,new ArrayBlockingQueue(6),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
ExecutorService的常用方法 方式二通过线程池的工具类 Executors 来创建。 4.为什么不推荐使用内置线程池
因为通过 Executors 创建出来的内置线程池会让我们不够熟悉线程池的运行规则会有资源耗尽的风险而通过 ThreadPoolExecutor 构造函数来创建线程池能让我们更加明确线程池的运行规则规避资源耗尽的风险。
4.线程池的参数 5.线程池的任务拒绝策略有哪些 ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出 RejectedExecutionException 异常。是默认的拒绝策略。 ThreadPoolExecutor.DiscardPolicy丢弃新任务但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy丢弃队列中等待最久的任务然后把当前任务加入队列中。 ThreadPoolExecutor.CallerRunsPolicy 在调用线程池的execute方法的线程中运行被拒绝的任务从而绕过线程池直接执行。
6.线程池常用的阻塞队列有哪些
新任务来的时候会先判断当前运行的线程数量是否达到核心线程数如果达到的话新任务就会被存放在阻塞队列中。 ArrayBlockingQueue是一个由数组结构组成的有界阻塞队列。它按照先进先出的原则对元素进行排序。 LinkedBlockingQueue是一个由链表结构组成的有界阻塞队列。它按照先进先出的原则对元素进行排序。因为其队列大小默认为 Integer.MAX_VALUE所以实际上它可以看作是无界队列。 SynchronousQueue是一个没有缓冲的阻塞队列每个插入操作必须等待另一个线程的移除操作反之亦然。因此该队列没有任何内部容量不能预先插入元素。 PriorityBlockingQueue是一个支持优先级排序的无界阻塞队列。默认情况下元素采取自然顺序排列也可以通过实现 Comparable 接口或者在构造时传入 Comparator 对象进行排序。
由 Excutors 创建出来的内置线程池选用了不同的阻塞队列不同的阻塞队列具有不同的特性和适用场景具体使用哪种队列需要根据实际需求来选择。例如 如果需要控制队列大小且按照先进先出的顺序处理任务可以选择 ArrayBlockingQueue 或 LinkedBlockingQueue 如果需要无缓冲等待两个线程之间的交互可以选择 SynchronousQueue 如果需要按照优先级排序执行任务可以选择 PriorityBlockingQueue。
7.线程池处理任务的流程了解吗 如果当前运行的线程数小于核心线程数那么就会新建一个线程来执行任务。 如果当前运行的线程数等于或大于核心线程数但是小于最大线程数那么就把该任务放入到任务队列里等待执行。 如果任务队列已经满了导致任务投放任务失败但是当前运行的线程数是小于最大线程数的就新建一个线程来执行任务。 如果当前运行的线程数已经等同于最大线程数了新建线程将会使当前运行的线程超出最大线程数那么当前任务会被拒绝饱和策略会调用RejectedExecutionHandler.rejectedExecution() 方法
8.如何设定线程池的大小
问题很多人可能会觉得把线程池配置过大一点比较好。但是线程数量过多的影响也是和我们分配多少人做事情一样对于多线程这个场景来说主要是增加了上下文切换成本。到底设置多少合适可以根据具体场景分析。
什么是上下文切换
多线程编程中一般线程的个数都大于 CPU 核心的个数而一个 CPU 核心在任意时刻只能被一个线程使用为了让这些线程都能得到有效执行CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用这个过程就属于一次上下文切换。概括来说就是当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态以便下次再切换回这个任务时可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。上下文切换通常是计算密集型的。也就是说它需要相当可观的处理器时间在每秒几十上百次的切换中每次切换都需要纳秒量级的时间。所以上下文切换对系统来说意味着消耗大量的 CPU 时间事实上可能是操作系统中时间消耗最大的操作。Linux 相比与其他操作系统有很多的优点其中有一项就是其上下文切换和模式切换的时间消耗非常少。
CPU的8核16线程是什么意思
8核16线程指的是CPU能并行运行16线程传统中一个核心只能运行一个线程但由英特尔公司开发的超线程技术硬件技术使得一个核心能并行运行多个线程。
9.如何设计一个能够根据任务的优先级来执行的线程池
这是一个常见的面试问题本质其实还是在考察求职者对于线程池以及阻塞队列的掌握。不同的线程池会选用不同的阻塞队列作为任务队列比如 FixedThreadPool 使用的是LinkedBlockingQueue无界队列由于队列永远不会被放满因此 FixedThreadPool 最多只能创建核心线程数的线程。ThreadPoolExecutor 的构造函数有一个 workQueue 参数可以传入任务队列。 设计方法如果要实现一个优先级任务线程池的话那可以考虑使用 PriorityBlockingQueue 优先级阻塞队列作为任务队列。PriorityBlockingQueue 是一个支持优先级的无界阻塞队列可以看作是线程安全的 PriorityQueue两者底层都是使用小顶堆形式的二叉堆即值最小的元素优先出队。不过PriorityQueue 不支持阻塞操作。要想让 PriorityBlockingQueue 实现对任务的排序传入其中的任务必须是具备排序能力的方式有两种 提交到线程池的任务实现 Comparable 接口并重写 compareTo 方法来指定任务之间的优先级比较规则。 创建 PriorityBlockingQueue 时传入一个 Comparator 对象来指定任务之间的排序规则(推荐)。
存在的问题 PriorityBlockingQueue 是无界的可能堆积大量的请求从而导致 OOM。 可能会导致饥饿问题即低优先级的任务长时间得不到执行。 由于需要对队列中的元素进行排序操作以及保证线程安全并发控制采用的是可重入锁 ReentrantLock因此会降低性能。
解决方法 对于 OOM 这个问题的解决比较简单粗暴就是继承PriorityBlockingQueue 并重写一下 offer 方法(入队)的逻辑当插入的元素数量超过指定值就返回 false 。 饥饿问题这个可以通过优化设计来解决比较麻烦比如等待时间过长的任务会被移除并重新添加到队列中但是优先级会被提升。 对于性能方面的影响是没办法避免的毕竟需要对任务进行排序操作。并且对于大部分业务场景来说这点性能影响是可以接受的。 PriorityQueue是queue系列中的一个集合