创建线程的三种方式
1.继承Thread
2.实现Runnable接口 实现Runnable只是规定了线程执行的内容,自身并没有执行的能力,需要通过Thread来执行。 为什么开发中常用Runnable?
1.执行的代码和执行者分离,执行代码和Thread是分开的。
2.资源共享多个Thread可以共用一个Runnable中的执行代码
3.实现Callable 可以返回线程的执行结果。 创建一个线程池并,将Callable实例交给线程池执行,返回结果存在Futur,Future.get就可以获取线程返回的执行结果 在没有返回时,主线程会等待。
线程通信
-
通过共享内存, 需要考虑并发问题。
-
通过网络。 也要考虑并发问题。
线程池
Eeecutor框架是java5中引入的。用来控制线程的启动,执行,和关闭,可以简化并发编程的操作,更易管理。有助于避免this逃逸问题。 Executor框架包括:线程池,Executor,Executors(工具类,以s结尾的一般是工具类,如Arrays,Collections)。
executor(Runnable r): 接受一个Runnable实例,执行任务,任务是实现了Runnable接口的类。 ExecutorService的shutdown()方法平滑第关闭ExecutorService。 ExecutorService的生命周期包括三种状态:运行、关闭、终止。 创建后便进入运行状态,调用shutdown()进入关闭状态。ExecutorService不在接收新任务,但已经提交的任务,要执行完。任务执行完后,进入终止状态。
Executors
提供了一系列工厂方法用于创建线程池。 返回的线程池都实现了ExecutorService接口。 可创建四种线程池。 FixedThreadPool 线程数固定 CachedThreadPool 线程数可调整。 可缓存,调用execute会重用以前构造的线程。如果没有线程可用,则创建一个新线程。 终止并从缓存中删除60s未使用的线程。 SingleThreadExecutor 只能存在一个线程的线程池,每次都要创建一个新线程。线程不可用时还得等待。 ScheduledThreadPool 可定时反复执行
Executor ExecutorService实现Executor
ExecutorService可以执行Runnable和Callable的任务 Callable必须通过submit方法执行。
将callable对象传给ExecutorService的submit方法后,callable的call方法会自动在一个线程上执行,并且返回Future对象。 同样,runnable的对象传给ExecutorService的submit方法,runnable的run方法会自动在一个线程上执行。
ExecutorService 执行任务最终还是调用Executor的execute方法,但execute只接受runnable,那ExecutorService为什么还能执行Callable任务? 实际上是ExecutorService内部进行了转换
核心类
ThreadPoolExecutor
内置了线程池的执行器 是构建线程的核心方法。 pool.execute()
当任务数少于核心线程数,就创建线程执行 当任务数大于核心线程数,就进入阻塞队列 当阻塞队列满了,且任务数少于最大线程数,就创建非核心线程执行。
创建线程池的七个参数
- corePoolSize:线程池中保持的线程数量。
- maximumPoolSize:允许的最大线程数
- keepAliveTime:空闲线程的活跃时间 如果线程池中的线程数数大于corePoolSize,并且有线程是空闲的,那么keepAliveTime时间后,这些空闲线程会被销毁。
- unit:空闲线程存活时间的时间单位。
- workQqueue:保存任务的队列,只保存用execute方法提交的runnable任务
- Handler拒绝策略:
当队列里放满了任务,线程都在工作时,新提交的任务就需要被拒绝。
怎么设置拒绝策略:
- 线程数到了最大,且不空闲,队列也满
- 调用了shutdown方法后
- ThreadFactory 给线程命名 设置线程是否后台运行 设置线程优先级
如何定义线程池参数
cpu密集:线程池大小为cpu数量加1 io密集:cpu数量cpu利用率(1+线程等待时间) 混合型: 拒绝策略:
-
默认采用AbortPolicy拒绝策略,在程序中排除RejectedException,不强制catch。
-
callerRunsPolicy: 会将任务交给调用execute方法的线程执行,此时主线程在一段时间内将不能提交新任务,从而使工作线程处理正在执行的任务。
-
自定义拒绝策略
-
如果任务不是特别重要,直接丢弃任务也可以
ThreadPoolTaskExecutor
是spring提供的,基于ThreadPoolExecutor实现,用了装饰者模式,增强了功能。
scheduleExecutorService
可以定时,周期执行任务
ScheduledThreadPoolExecutor
线程池+执行器+定时,周期执行
关闭线程池
shutdown: 加锁,将线程池状态改为SHUTDOWN,中断空闲线程 shutdowNnow:加锁 将线程池状态改为STOP,中断所有线程。 中断线程,不代表线程立即结束,需要线程配合中断响应。 线程中断机制:thread # interrupt
线程池状态 1.创建后进入RUNNING 2. SHUTDOWN 不接受新任务,会把工作队列中的任务执行结束 3. STOP 不接受新任务,还会中断执行工作队列中的任务 4. TIDYING 所有任务都结束 5. TERMINATED
同步关键字
CountDownLatch
同步工具类。 允许线程可以一直等待其他线程执行完。 基于线程计数器来实现并发访问控制,主要用于主线程等待其它子线程执行完后在执行。 实现式: 在主线程中定义CountDownLatch,并将线程计数器初始值设置为子线程数,多个子线程并发执行。 每个字线程执行完,都会把CountDown-1.知道计数器为0,表示所有主线程执行完了。 此时等待的主线程会被唤醒并执行。
CAS
compare and swap 要更新的变量,旧值,新值 原子读改写操作。JVM使用这些指令来实现无锁并发。
竞争一般时,CAS性能远超基于锁的计数器。看起来他的指令更多,但无需上下文切换和线程挂起。jvm内部的代码路径实际很长。
为确保正常更新,可能将CAS操作放到for循环,语法结构看,CAS比使用锁更复杂,得考虑失败情况。 但基于CAS的原子操作,性能基本超过基于锁的计数器,即便只有很小的竞争或不存在竞争。
特性: CAS操作用了乐观锁思想,总认为自己可以成功完成操作。 有多个线程使用CAS操作同一个变量时,只有一个会成功更新。 失败的线程不会被挂起,可以再次尝试。
自旋等待 在多线程环境下,在有多个线程同时执行这些类的实例包含的方法时。 即在某个线程进入方法中执行其中的指令时,不会 被其他线程打断;而别的线程就像自旋锁一样,一直等到该方法执行 完成才由JVM从等待的队列中选择另一个线程进入。 相对于synchronized阻塞算法,CAS是非阻塞算法的一种常见实现。
AQS
abstract queued synchronizer 抽象队列同步器,通过维护一个共享资源状态,和一个先进先出队列来实现一个多线程访问共享资源的框架。
原理 为每个共享资源设置一个共享资源锁,线程在需要访问共享资源时首先需要获取共享资源锁。获取到了,就可以在当前线程中使用该共享资源,并且将共享资源设置为锁定状态。获取不到,就把线程放入线程等待队列,等待下一次资源调度。
许多同步类的实现,都依赖AQS,如Reentrantlock,Semaphore,CountDownlatch。
state AQS维护了一个volatile int类型的变量state,用于表示当前的同步状态。Volatile不能保证原子性,但能保证state的可见性。
state可以进行compareAndSetState()操作。
AQS只是一个接口,具体操作交由自定义同步器去实现。 自定义同步器的主要方法: 1.查询线程是否在独占资源,用到condition才需要去执行 2.尝试获取锁 3.尝试释放资源 4.释放资源后允许唤醒后续等待线程
AQS定义了两种资源共享方式,独占式和共享式。 独占式:只有一个线程执行,实现方式为ReentrantLock。 共享式:多个线程可共同执行。实现方式为Semaphore和CountDownLatch。
同步器的实现是AQS的核心。
ReentrantLock
ReentrantLock采用独占方式:ReentrantLock中的state初始值为 0时表示无锁状 态 。 在 线 程 执 行 tryAcquire() 获 取 该 锁 后 ReentrantLock 中 的 state+1,这时该线程独占ReentrantLock锁,其他线程在通过 tryAcquire()获取锁时均会失败,直到锁被释放,state再次为0。 该线程在持有锁期间,可以重复获取锁,每次获取state+1. 但获取多少次锁就要释放多少次锁,这样才能保证state最终为 0。
CountDownLatch
采用共享共享方式:CountDownLatch将任务分为N个子线程去执行,将state也初始化为N,N与线程的个数一致,N个线程是并行执行的。 主线程调用latch.await()阻塞等待 每个子线程都在执行完后countDown()一次,state会执行CAS操作,并减1. 在所有子线程都执行完成后,(state=0)时会unpark主线程。主线程会从await返回,继续执行后续动作。
一般来说,自定义同步器要么采用独占方式,要么采用共享方式。 但也可以同时实现。
volatile
变量对所有线程可见,一个线程改变了变量,其他线程可以立即获取新的值。 进制指令重排:volatile变量不会被缓存在寄存 器中或者对其他处理器不可见的地方,因此在读取volatile类型的变量 时总会返回最新写入的值。 在访问volatile变量时不会执行加锁操作,也就不会执行线程 阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同 步机制。 volatile主要适用于一个变量被多个线程共享,多个线程均可 针对这个变量执行赋值或者读取的操作。
volatile 在 某 些 场 景 下 可 以 代 替 synchronized,但是volatile不能完全取代synchronized的位置, 只有在一些特殊场景下才适合使用volatile。比如,必须同时满足下面 两个条件才能保证并发环境的线程安全。
- 对变量的写操作不依赖于当前值(比如i++),或者说是单纯的变量赋值(boolean flag=true)。
- 该变量没有被包含在具有其他变量的不变式中,也就是说在不 同的volatile变量之间不能互相依赖,只有在状态真正独立于程序内的 其他内容时才能使用volatile。
多线程共享数据
数据在多线程环境下的一致性和安全性。 实现方式:
-
将数据封装在一个类中,数据的操作写在方法中,方法要加上synchronized修饰符。
-
把这个类作为Runnable的成员变量。
ThreadLocal
可以将数据缓存在某个线程内部,该线程可以在任意时刻任意方法取出数据。
ThreadLocal是由ThreadLocalMap类实现的。 每个Thread内部都维护了一个ThreadLocalMap类,map的key是ThreadLocal,value是要缓存的值。
ThreadLocal的set方法: 先取出ThreadLocal所在的线程,再从线程中取出ThreadLocalMap,再往ThreadLocalMap中添加键值对。 使用完ThreadLocal后调用remove方法清楚键值对。
应用场景:连接管理。。。