线程池&线程并发库

线程与进程的区别

进程:进程是资源(CPU,内存等)分配的最小单位,它是程序执行时的一个实例,进程是不能共享资源的;

线程:线程是程序执行的最小单位;

一个进程可以包含多个线程。

创建线程的方式

1.继承Thread

2.实现Runnable接口

3.实现Callable接口

4.通过线程池创建

上述创建线程的方式可能遇见的问题

1.实现Runnable接口和Callable接口的区别

实现Runnable接口没有返回值,实现Callable接口有返回值,Callable接口还可以抛出异常

2.继承Thread创建线程和实现接口创建线程的区别

Thread的继承是单继承,接口则是多实现,实现接口可以实现资源共享(因为实现接口会new一个实现接口的类交给Thread,实际上创建线程还是由Thread来创建的)

3.调用run方法和start方法的区别

run()方法只是一个普通的方法调用,不会创建新的线程,而start方法会开启一个新的线程

4.Thread类常用的一些方法

getName()

返回此线程的名称

interrupt() 中断这个线程
isAlive()测试这个线程是否还活着      
join()等待这个线程死亡
run()如果这个线程使用单独的Runnable运行对象构造,则调用该Runnable对象的run方法;否则,此方法不执行任何操作并返回
sleep()睡眠一下,暂停执行
start()导致此线程开始执行;java虚拟机调用此线程的run方法
yield()对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器

线程的状态

新建:新创建一个线程对象

就绪:线程对象创建之后,其他线程

运行:就绪状态的线程获取CPU时间片,执行程序代码

阻塞:阻塞状态是线程因为某种原因放弃了CPU使用权,也即让出了CPU timeslice,即暂时停止运行,直到线程进入可运行(Runnable)状态,才有机会再次获得CPU timeslice转到运行(Running)状态

阻塞又分几种阻塞

同步阻塞Synchronized同步锁;运行(Running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中
等待阻塞wait;运行(Running)的程序执行o.wait()方法,JVM会把线程放入等待队列(waitting  queue)中
其他阻塞sleep,join;运行(Running)的线程执行Thread.sleep(Long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程设置为阻塞状态,当sleep()状态超时,join()等待线程终止或者超时,或者I/O处理完毕时,线程重新转入可运行(Runnable)状态

死亡:线程run(),main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期,死亡的线程不可再次复生

wait与sleep的区别

1.sleep方法来自Thread类,wait方法来自Object类

2.sleep方法没有释放锁,wait释放了锁,使得其它线程可以使用同步控制块或者方法

3.wait会在等待时将线程挂起,而不是忙等待,并且只有notify或者notifyAll到达时才会唤醒,实际上只有同步控制块或者同步控制方法里面才能wait,notify和notifyAll

4.它们两个都需要捕获异常

5.它们两个都可以被interrupted方法中断

线程安全问题

多个线程同时访问同一个资源的时候,可能出现期待结果和实际结果不一致的情况就说明出现了线程安全问题

解决方案

第一种:共享:

加锁

锁的分类:

宏观上根据是否要锁住同步资源分为悲观锁和乐观锁;

锁住同步资源失败不阻塞时分为自旋锁和适应性自旋锁

无锁不锁住资源,多个线程只有一个能修改成功,其他线程会重试
偏向锁同一个线程执行同步资源时自动获取资源
轻量级锁多个线程竞争同步资源时,没有获取资源的线程自旋等待锁释放
重量级锁多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒
公平锁多个线程竞争锁需要排队
非公平锁先尝试插队,插队失败了再排队
可重入锁一个线程的多个流程可以获取同一把锁
非可重入锁一个线程的多个流程b可以获取同一把锁
共享锁多个线程能共享一把锁
排他锁

多个线程不能共享同一把锁

悲观锁(Synchronized)

使用方式(保证监听对象的唯一性):

1.可以用在代码块前面

2.可以用在静态方法前面

3.可以用在普通方法前面

Synchronized的实现原理:

基于JVM的内置实现,通过内部对象Monitor(监视器锁)实现,依赖底层操作系统(互斥锁)实现,分为加锁和释放锁两个过程,加锁急Monitor enter计数为0的时候加1,释放锁就是Monitor exit计数变为0,这些操作都是JVM去实现的,它是一个重量级锁性能较低,JDK1.5之后做了重大优化

 

Synchronized的膨胀过程:

无锁一一>偏向锁一一>轻量级锁一一>重量级锁

Synchronized和Lock的区别:

1.synchronized是一个关键字,在JVM的层面上;而Lock是一个类

2.synchronized是通过JVM一个monitor exit释放锁的;Lock是在finally里面释放锁的

3.synchronized会造成阻塞,比如A获得锁B就要等待,A阻塞B就会一直等待;Lock是分情况而定,大致是可以尝试获取锁,线程可以不用一直等待

4.synchronized无法判断锁的状态;Lock可以判断锁的状态

5.synchronized可重入,不可中断,不公平;Lock可以判断锁的状态        

6.synchronized少量同步;Lock大量同步

第二种:隔离:

ThreadLocal:

1.使用方式:

ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能;

get()线程获取该ThreadLocal对应的变量
set(T value)线程设置该ThreadLocal对应的变量
remove()线程移除该ThreadLocal
withInitial()这个一般生成ThreadLocal时使用初始变量

2.如何实现变量副本/线程隔离:

初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals,然后在当前线程里面,如果要使用副本变量,就可以通过get()方法在threadLocals里面查找;底层通过Map实现,set的时候以当前线程或者map,将当前线程对象设置为key,get的时候,以当前线程对象获取map,map中以当前线程对象为key获取值

3.使用ThreadLocal可能造成的问题:

ThreadLocalMap中用于存储数据的entry定义,使用了弱引用,可能造成内存泄露

解决方案:

使用完线程共享变量后,显式调用ThreadLocalMap.remove方法清楚线程共享变量

ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了

线程并发库

1.Lock锁:

显式锁:需要自己显式的加锁,比如synchronized使用的JVM内置锁的实现的,他就不是显式锁

2.Atomic:

为了实现原子性操作提供的一些原子类,使用的是乐观锁实现

3.并发容器类:

并发容器都是线程安全的,比如在多线程中可以使用ConcurrentHashMap代替HashMap

线程池

1.线程池的核心参数

CorePoolSize核心线程数,不会被销毁
MaximumPoolSize最大线程数(核心+非核心),非核心线程数用完之后达到空闲时间会被销毁
KeepAliveTime非核心线程的最大空闲时间,到了这个空闲时间没被使用,非核心线程销毁
Unit空闲时间单位
WorkQueue是一个BlockingQueue阻塞队列,超过核心线程数的任务会进入队列排队
ThreadFactory使用ThreadFactory创建新线程,推荐使用Executors.defaultThreadFactory
Handler拒绝策略,任务超过最大线程数+队列排队数,多出来的任务该如何处理取决于Handler

 

2.JDK自带的四种线程池

①.CachedThreadPool:可缓存:

这种线程池内部没有核心线程,线程的数量是有限制的,最大是Integer最大值

在创建任务时,若有空闲线程时则复用空闲的线程(缓存线程),若没有则新建线程

没有工作的线程(闲置状态)在超过了60s还不做事,就会销毁

适用于执行很短期异步的小程序或者负载较轻的服务器

②.FixedThreadPool :固定长度:

该线程池的最大线程数等于核心线程数,所有在默认的情况下,该线程池的线程不会因为闲置状态超时而被销毁

如果当线程数小于核心线程数,并且也有闲置线程的时候提交了任务,这时也不会去复用之前的闲置线程,会创建新的线程去执行任务(必须达到最大核心数才会复用线程),如果当前执行的任务数大于了核心线程数,大于的部分就会进入队列等待,等着有闲置的线程来执行这个任务

适用于执行长期的任务,性能好很多

③.SingleThreadPool:单个:

有且仅有一个工作线程执行任务

所有任务按照指定的顺序执行,即遵循队列的入队出队规则

适用于一个任务一个任务执行的场景,如同队列

④.ScheduledThreadPool:可调度:

DEFAULT_KEEPALIVE_MILLIS就是默认10L,这里就是10秒。这个线程池有点像是CachedThreadPool和FixedThreadPool 结合了一下

不仅设置了核心线程数,最大线程数也是Integer.MAX_VALUE

这个线程池是上述4个中唯一有延迟执行和周期执行任务的线程池

适用于周期性执行任务的场景(定期的同步数据)

3.工作队列有哪些

SynchronousQueue

这个队列比较特殊,它不会保存提交的任务而是将直接新建一个线程来执行新来的任务      

LinkedBlockingQueue

基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE

ArrayBlockingQueue

基于数组的先进先出队列,此队列创建时必须指定大小

4.拒绝策略有哪些?分别的特点是什么

拒绝策略特点

AbortPolicy

丢弃任务,抛出异常

DiscardOldestPolicy

抛弃队列中旧的任务执行新添加的,不会抛出异常

DiscardPolicy

队列满了以后直接丢弃任务,不会抛弃异常

CallerRunsPolicy

当队列满了以后,由提交任务的线程执行任务,不会抛出异常,不会丢弃任务,所有的都会被执行到

5.线程池中常用的方法

Execute

方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行

Submit

方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,实际上它还是调用的execute()方法,只不过它利用了Future来获取任务执行结果

Shutdown

不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

ShoutdownNow

立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

isTerminated

调用ExecutorService.shutdown方法的时候,线程池不再接收任何新任务,但此时线程池并不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。在调用shutdown方法后我们可以在一个死循环里面用isTerminated方法判断是否线程池中的所有线程已经执行完毕,如果子线程都结束了,我们就可以做关闭流等后续操作了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值