知识总结-多线程编程

多线程编程

线程是jvm调度的最小单位,进程是由线程组成的。
线程的状态: 
java线程的状态可以通过调用相应thread的getState方法获取。该方法的返回值类型Thread.State是一个枚举类型,包含的状态有以下几种。

NEW

一个刚创建而未启动的线程处于该状态。由于一个线程实例只能被启动一次,因此一个线程只可能有一次处于该状态。

runnable

这是一个复合状态,包括READY和RUNNING。
READY。表示该状态的线程可以被jvm的线程调度器进行调度而使之处于RUNNING状态。
RUNNING。表示该线程正在运行,即相应线程的run方法正在被执行。当Thread实例的yield方法被调用时或由于线程调度器的原因,相应线程的状态会由RUNNING转为READY。

blocked

一个线程发起了一个阻塞式io操作后,或者试图去获取以一个由其他线程持有的锁时,相应的线程会处于该状态。处于该状态的线程并不会占用CPU资源。当相应的io操作完成后,或者相应的锁被其他线程释放后,该线程的状态又可以转换为RUNNABLE。

waiting

一个线程执行了某些方法调用之后就会处于这种无限等待其他线程执行特定操作的状态。这些方法包括:Object.wait(),Thread.join()…能使相应线程从WAITING转换到RUNNABLE的相应方法包括:Object.notify(),Object.notifyAll()…

timed waiting

与WAITING状态类似,差别在于等待时间非无限等待,指定时间过后,自动转为RUNNABLE。

terminated

已经执行结束的线程处于该状态。同NEW一样,有且仅有一次。run方法正常返回或者由于异常终止都会导致该状态。

synchronized和volatile

了解这两个关键字之前,我们需要先有以下几个概念,原子性、内存可见性和重排序。

原子性:原子操作是指相应的操作是单一不可分割的操作。例如:count++就不是原子操作,因为该操作分为三步,1)读取count的值,2)count做++运算,3)把运算后的值赋予count。在多线程环境下,该操作可能会收到其他线程的干扰,导致我们不能得到想要的结果。
内存可见性:CPU在执行代码的时候,为了减少变量访问的时间消耗,可能会将代码中访问的变量的值缓存到该CPU的缓存区。因此代码访问或者写入的变量,可能只是在缓存区而不是主内存。这就导致了一个CPU对变量的操作可能无法被其他CPU感知。
重排序:编译器和CPU为了提高指令的执行效率可能会进行指令重排序,意思是一段代码的实际执行顺序会被重新排序。例如:People p = new People();正常地执行流程为:1)创建People的实例,2)将实例赋予变量p。但是由于指令重排的作用,实际实行顺序可能是:1)分配一段用于储存People实例的内存空间,2)将对该空间的引用赋值给变量p,3)创建People的实例。因此,当其他线程访问变量p时,可能此时p实例的初始化尚未完成。
synchronized关键字实现操作原子性的本质是通过该关键字所包括的临界区的排他性保证在同一时刻只有一个线程能执行临界区中的代码。该操作保证了原子性和内存可见性。
volatile关键字保证了内存可见性,即,一个线程对一个volatile关键字修饰的变量的值的更改对于其他访问该变量的线程总是可见的。其核心机制为当一个线程更改了volatile关键字修饰的变量的值时,该值会被写入主内存而不仅仅时该线程的CPU缓存区,而其他CPU的缓存区中储存的该变量的值就会失效。这就保证了当任意线程访问一个volatile修饰的值时,那一刻得到的值一定是最新的。但是如果在读取后,有线程对其进行了修改,就无法保证操作的原子性了。volatile关键字的另一个作用是它禁止了指令重排序。
synchronized关键字技能保证操作的原子性,也能保证内存可见性,但是会导致上下文切换。volatile关键字仅能保证内存可见性。
线程池
corePoolSize 核心线程池大小,线程池创建之后,线程池中的线程数为0,当任务过来就会创建一个线程去执行,直到线程数达到corePoolSize 之后,到达的任务就会被放在队列中。换句更精炼的话:corePoolSize 表示允许线程池中允许同时运行的最大线程数。如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
maximumPoolSize :线程池允许的最大线程数,他表示最大能创建多少个线程
keepAliveTime :表示线程没有任务时最多保持多久然后停止。默认情况下,只有线程池中线程数大于corePoolSize 时,keepAliveTime 才会起作用。换句话说,当线程池中的线程数大于corePoolSize,并且一个线程空闲时间达到了keepAliveTime,那么就是shutdown。
workQueue :一个阻塞队列,用来存储等待执行的任务,当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能
拒绝策略

AbortPolicy:丢弃任务并抛出RejectedExecutionException
CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
DiscardOldestPolicy:丢弃队列中最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
DiscardPolicy:丢弃任务,不做任何处理。

常见的四种线程池
newFixedThreadPool 固定大小的线程池,可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。
newSingleThreadExecutor 单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。
newCachedThreadPool 缓存线程池,缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,最大线程数为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列, 他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。
newScheduledThreadPool 定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间。

线程池 submit excute 区别

submit 方法入参 支持 callable和 runnable,而excute方法 只支持runnable 。submit方法返回一个 Future 通过 get方法可以获得线程执行结果,而excute没有返回值。submit不会抛出异常而是把异常保存在成员变量中,在Future .get把异常抛出来。
相关类 runnable callable futureTask runnablefuture future threadpoolExcutor AbstractExecutorService ExcutorService
在Springboot 中可以通过@async 注解信息多线程调用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值