什么是多线程?
多线程是利用cpu的多核心技术,使多线程实现线程的并发执行
java四种线程池
newCachedThreadPool
创建可缓存的线程,底层是依靠 SynchronousQueue 实现的,创建线程数量几乎没有限制(最大为 Integer.MAX_VALUE)。如果长时间没有往线程池提交任务,即如果工作线程空闲了指定时间(默认1分钟),该工作线程自动终止。终止后如果又有了新的任务,则会创建新的线程。在使用 CachedTreadPool 时,要注意控制任务数量,否则由于大量线程同时运行,很有可能造成系统瘫痪。
newFixedThreadPool
创建指定数量的工作线程,底层是依靠 LinkedBlockingQueue 实现的,没提交一个任务就创建一个工作线程,当工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。在线程空闲时,不会释放工作线程,还会占用一定的系统资源。
newSingleThreadExecutor
创建单线程,底层是 LinkedBlockingQueue 实现的,它只会用一个工作线程来执行任务,保证所有的任务按指定顺序执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。最大的特点是可保证顺序地执行各个任务,并在任意时间是不会有过个线程活动的。
newScheduleThreadPool
创建一个定长的线程池,支持定时以及周期性的任务调度。
线程池工作原理
一 线程池创建
1、corePoolSize
线程池的核心线程数,每个线程都会存在即使没有任何工作也会在等任务
2、maximumPoolSize
最大线程数,线程池的上限
3、keepAliveTime
线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则线程退出
4、unit
指定的线程存活时间,比如秒:TimeUnit.SECONDS。
5、workQueue
一个阻塞队列提交的任务会放到这个队列里
6、threadFactory
线程工厂,用来创建线程,主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。
7、handler
拒绝策略,当线程池里线程被耗尽,且队列也满了的时候会调用。
二、线程池执行流程
说明
1、提交任务当向线程池提交一个新的任务时,线程池有三种处理情况,分别是:创建一个工作线程来执行该任务、将任务加入阻塞队列、拒绝该任务。提交任务的过程也可以拆分成以下几个部分:当工作线程数小于核心线程数时,直接创建新的核心工作线程当工作线程数不小于核心线程数时,就需要尝试将任务添加到阻塞队列中去如果能够加入成功,说明队列还没有满,那么需要做以下的二次验证来保证添加进去的任务能够成功被执行验证当前线程池的运行状态,如果是非RUNNING状态,则需要将任务从阻塞队列中移除,然后拒绝该任务验证当前线程池中的工作线程的个数,如果为0,则需要主动添加一个空工作线程来执行刚刚添加到阻塞队列中的任务如果加入失败,则说明队列已经满了,那么这时就需要创建新的“临时”工作线程来执行任务如果创建成功,则直接执行该任务如果创建失败,则说明工作线程数已经等于最大线程数了,则只能拒绝该任务了整个过程可以用下面这张图来表示:
2、创建工作线程创建工作线程需要做一系列的判断,需要确保当前线程池可以创建新的线程之后,才能创建。首先,当线程池的状态是 SHUTDOWN 或者 STOP 时,则不能创建新的线程。另外,当线程工厂创建线程失败时,也不能创建新的线程。还有就是当前工作线程的数量与核心线程数、最大线程数进行比较,如果前者大于后者的话,也不允许创建。除此之外,会尝试通过 CAS 来自增工作线程的个数,如果自增成功了,则会创建新的工作线程,即 Worker 对象。然后加锁进行二次验证是否能够创建工作线程,最后如果创建成功,则会启动该工作线程。3、启动工作线程当工作线程创建成功后,也就是 Worker 对象已经创建好了,这时就需要启动该工作线程,让线程开始干活了,Worker 对象中关联着一个 Thread,所以要启动工作线程的话,只要通过 worker.thread.start() 来启动该线程即可。启动完了之后,就会执行 Worker 对象的 run 方法,因为 Worker 实现了 Runnable 接口,所以本质上 Worker 也是一个线程。通过线程 start 开启之后就会调用到 Runnable 的 run 方法,在 worker 对象的 run 方法中,调用了 runWorker(this) 方法,也就是把当前对象传递给了 runWorker 方法,让他来执行。4、获取任务并执行在 runWorker 方法被调用之后,就是执行具体的任务了,首先需要拿到一个可以执行的任务,而 Worker 对象中默认绑定了一个任务,如果该任务不为空的话,那么就是直接执行。执行完了之后,就会去阻塞队列中获取任务来执行,而获取任务的过程,需要考虑当前工作线程的个数。如果工作线程数大于核心线程数,那么就需要通过 poll 来获取,因为这时需要对闲置的线程进行回收;如果工作线程数小于等于核心线程数,那么就可以通过 take 来获取了,因此这时所有的线程都是核心线程,不需要进行回收,前提是没有设置 allowCoreThreadTimeOut
什么是CAS(compare and swap)?
CAS 最终指令:lock(原子性,当执行cmpxchg 操作时,当前cpu不予许修改) cmpxchg (非原子性)
ThreadLocal作用
ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定。
volatile作用
1、防止指令重排序
2、lock
volatile/synchronize --> lock指令
1.把当前处理器缓存行的数据写会系统内存
2.使得cpu内的缓存地址失效
Java强软弱虚四中引用的使用场景
一、引用强度排序
强引用>软引用>弱引用>虚引用
二、强引用:
1. 正常创建的对象,只要引用存在,永远不会被GC回收,即使OOM
Object obj = new Object();
2. 如果要中断强引用和某个对象的关联,为其赋值null,这样GC就会在合适的时候回收对象
3. Vector类的clear()方法就是通过赋值null进行清除
三、软引用
1. 内存溢出之前进行回收,GC时内存不足时回收,如果内存足够就不回收
2. 使用场景:在内存足够的情况下进行缓存,提升速度,内存不足时JVM自动回收
Object obj = new Object();
SoftReference sf = new SoftReference(obj);
sf.get();//有时候会返回null
3. 可以和引用队列ReferenceQueue联合使用,如果软引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中
四、弱引用
1. 每次GC时回收,无论内存是否足够
2. 使用场景:a. ThreadLocalMap防止内存泄漏 b. 监控对象是否将要被回收
Object obj = new Object();
WeakReference wf = new WeakReference(obj);
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
System.gc(); //通知JVM的gc进行垃圾回收,但JVM不一定会立刻执行
wf.get();//此时会返回null
3. 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中
五、虚引用
1. 每次垃圾回收时都会被回收,主要用于监测对象是否已经从内存中删除
2. 虚引用必须和引用队列关联使用, 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中
3. 程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动
Object obj = new Object();
PhantomReference pf = new PhantomReference(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除
线程、进程、内存、cpu架构图
CPU的核心数、线程数的关系和区别
关系:
1、线程数可以模拟出不同的CPU核心数。
CPU的核数是指硬件上有多个核,线程数可以模拟多个核的功能。线程越多,就越有利于同时运行多个程序,因为线程数等于CPU在某一时刻可以同时并行处理的任务数。
2、对于一个CPU,线程数总是大于或等于核心数的。一个内核至少对应一个线程,但通过超线程技术,一个内核可以对应两个线程,即可以同时运行两个线程。
区别:
1、不同的存在形式
(1)CPU的核心数是指硬件上的真实对象。
(2)CPU线程数只是一个逻辑概念,不是一个真正的对象,只是为了更好地描述CPU的运行能力。
2、线程数对于不同的CPU类型存在不同的状态
(1)对于英特尔CPU:除了核心数之外,还可以使用线程数的概念,因为它是通过英特尔超线程技术实现的。
(2)对于AMDCPU:只有内核数,没有线程数的概念。因为AMDCPU没有超线程技术,一个CPU核对应一个线程。
3、出现原因不同
(1)核心数产生的原因:提高处理器主频的技术遇到了瓶颈。为了在“多核”的方向上发展,现有的产品可以发展成一个具有更强大理论性能而没有大规模发展的多核处理器系统。因此,“核心数”一词应运而生。
(2)线程数量的原因:为了进一步提高计算机的多任务处理能力。线程越多,同时运行多个程序就越好。
参考:
https://www.cnblogs.com/yw-ah/p/5830458.html
https://www.cnblogs.com/dolphin0520/p/3784171.html
https://blog.csdn.net/u_my_heart/article/details/90648193