并行和并发
并发:指两个或多个事件在同一个时间段内发生。(单核)
并行:指两个或多个事件在同一时刻发生(同时发生,多核)
自定义线程的方式
创建线程方式有四种:
继承Thread类,重写run方法
实现Runnable接口,实现run方法
实现Callable接口,通过包装器FutrueTask创建线程(有返回值)
线程池创建
线程的生命周期
新建(NEW),就绪(Runnable),运行(Running),阻塞(Blocked)(又分为 Blocked,waiting,time-waiting),死亡(Dead/TERMINATED)
线程的常见方法
sleep(): 每一个对象都有一个锁,sleep不会释放锁,使线程停止运行一段时间,将处于阻塞状态,如果调用了sleep方法之后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行!线程由运行状态进入阻塞状态,时间一到,再回到预备状态,等待CPU的重新调度。
join(): join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
yield(): 线程直接由运行状态跳回预备状态。让当前正在执行线程暂停,不是阻塞线程,而是将线程转入就绪状态。调用了yield方法之后,如果没有其他等待执行的线程,此时当前线程就会马上恢复执行!
setDaemon(): 线程分为用户线程和守护线程。虚拟机必须确保用户线程执行完毕。虚拟机不用等待守护线程执行完毕,如后台记录操作日志、监控内存使用等。
1.可以将指定的线程设置成后台线程,守护线程
2.创建用户线程的线程结束时,后台线程也随之消亡
3.只能在线程启动之前把它设为后台线程
setPriority(): 线程的优先级代表的是概率,范围从1到10,默认为5。Java提供:一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。
如何保证线程的执行顺序
方案一:使用join()方法
方案二:单线程线程池
守护线程和用户线程
在 Java 中通常有两种线程:守护线程(Daemon Thread)和用户线程(User Thread)。
守护线程:如果只剩下守护线程未离开,JVM是可以离开的,守护线程是JVM中所有用户线程的保姆。,在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT 线程都是守护线程
用户线程:当存在任何一个用户线程未离开,JVM是不会离开的。可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作。如我们使用 Thread 创建的线程在默认情况下都属于用户线程
sleep和wait的区别
1,这两个方法来自不同的类:wait是Object下的方法和sleep是Thread下的方法。
2,最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3,wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在
任何地方使用
synchronized(x){
x.notify()
//或者wait()
}
4,sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
run和start的区别
start()
用 start方法来启动线程,是真正实现了多线程, 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法。但要注意的是,此时无需等待run()方法执行完毕,即可继续执行下面的代码。所以run()方法并没有实现多线程。
run()
run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码。
区别
1、线程中的start()方法和run()方法的主要区别在于,当程序调用start()方法,将会创建一个新线程去执行run()方法中的代码。但是如果直接调用run()方法的话,会直接在当前线程中执行run()中的代码,注意,这里不会创建新线程。这样run()就像一个普通方法一样。
2、另外当一个线程启动之后,不能重复调用start(),否则会报IllegalStateException异常。但是可以重复调用run()方法。
总结起来就是run()就是一个普通的方法,而start()会创建一个新线程去执行run()的代码。
如何保证线程安全
1.Confinement
限制数据共享。
将可变数据限制在单一线程内部,避免竞争。核心思想就是线程之间不共享可变数据类型。
2.Immutable
将可变数据类型改为Immutable类型。
避免多线程间的race condition。
3.Threadsafe data type
共享线程安全的可变数据。
如果必须要在多线程间使用mutable的数据类型,必须要使用线程安全的数据类型。在JDK的类文档中,记录着是否线程安全。如List,Set,Map等集合类,都是线程不安全的。
4.Synchronization
通过锁的机制共享不安全的可变数据。
常见的线程池有哪些
1.单线程线程池
2.固定大小的线程池(核心线程数=最大线程数)
3.可缓存的线程池
4.可定时和周期执行任务的线程池
如何自定义线程池 —
1.使用ThreadPoolExecutor创建线程池。
2.通过实现 ThreadFactory 接口,实现自定义线池创建工厂。
3.通过实现 RejectedExecutionHandler 接口,实现自定义线程池拒绝策略。
自定义线程池的七大参数 —
1、corePoolSize:核心线程数,线程池中最少线程,核心线程不会被回收。
2、maximumPoolSize:最大线程数,线程池中最多线程,包含核心线程数,不能小于核心线程数。
3、keepAliveTime:非核心线程(除去核心线程之外的线程)存活时间,如果非核心线程的空闲时间大于此参数,将会被回收。
4、TimeUnit:时间单位,参数keepAliveTime的时间单位。
5、BlockingQueue:阻塞工作队列,当来一个新的线程任务时,如果当前没有空闲线程,此线程任务将会进入阻塞工作队列中进行等待。
6、ThreadFactory:线程工厂,用于创建线程,自定义线程的名称。(需要实现ThreadFactory接口,实现newThread()方法,在此方法中,新建Thread对象,通过调用setName()方法给线程定义名称。)
7、RejectedExecutionHandler:拒绝策略,当线程池中没有空闲线程,且阻塞工作队列已满,且最大线程数也已超出,此时再来线程任务将执行拒绝策略。
有四种拒绝策略:第一种:AbortPolicy,丢弃任务并且抛出异常(默认方式)。第二种:DiscardPolicy,丢弃任务不抛异常。第三种:DiscardOldestPolicy,将阻塞工作队列中的队头任务丢弃,将当前任务加入阻塞工作队列。第四种:CallerRunsPolicy,谁调用谁处理。
线程在线程池中的执行流程 —
1.在向线程池提交任务时,判断是否存在空闲线程
空闲线程存在,分配空闲线程,执行该线程
2.空闲线程不存在,判断已有线程是否超出设置的核心线程数
核心线程数未超出,创建新的核心线程去执行该线程任务
已超出,判断任务队列是否已满
3.任务队列未满,将该线程任务放入工作队列中等待,等有其他线程执行任务后,归还线程,工作队列中的任务依次出队,获取空闲线程,执行线程任务
4.任务队列满,判断是否超出最大线程数
最大线程数未超出,创建非核心线程,执行线程任务,该线程执行完任务,空闲的时间超出指定时间后将被回收
5.已超出,执行拒绝策略
常见拒绝策略
1 ThreadPoolExecutor.AbortPolicy 默认拒绝策略,拒绝任务并抛出任务
2 ThreadPoolExecutor.CallerRunsPolicy 使用调用线程直接运行任务
3 ThreadPoolExecutor.DiscardPolicy 直接拒绝任务,不抛出错误
4 ThreadPoolExecutor.DiscardOldestPolicy 触发拒绝策略,只要还有任务新增,一直会丢弃阻塞队列的最老的任务,并将新的任务加入
ThreadLocal – 频率不高
ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。
ThreadLocal 适用于如下两种场景
1、每个线程需要有自己单独的实例
2、实例需要在多个方法中共享,但不希望被多线程共享