SpringBoot加载Bean的几种方式
- 通过@ComponentScan注解,在@SpringBootApplication里面就包含了@ComponentScan,把工程中的@Component、@Configuration等注解找到,加入Spring容器。
- @Bean注解
- @Import注解
- @EnableAutoConfiguration注解,spring-boot会利用AutoConfigurationImportSelector搜索所有jar包中META-INF文件夹中spring.factories
voalite
volatile修饰的变量具有可见性,对变量的操作在内存中进行,对所有线程共享变量,并且局部阻止了指令重排的发生,限制编译器对修饰变量的相关读写操作和指令重排。
volatile和synchronized的区别
- volatile仅能使用在变量上,synchronized可以使用在变量和方法与类级别上;
- volatile仅能实现变量的可见性,不能保证原子性,synchronized可以保证变量的可见性和原子性;
- volatile不会造成线程阻塞,synchronized可能会造成线程阻塞(因为volatile只是将当前变量的值及时告知所有线程,而synchronized是锁定当前变量不让其它线程访问);
- volatile标记的变量不会被编译器优化(因为不能指令重排),synchronized标记的变量可以被编译器优化;
- volatile修饰变量适合于一写多读的并发场景,而多写场景一定会产生线程安全问题(因此使用volatile而不是synchronized的唯一安全情况是类中只有一个可变的域)。
- 因为所有的操作都需要同步给内存变量,所以volatile一定会使线程的执行速度变慢。
volatile与static
- 保证一致性,不保证唯一性,多个实例有多个volatile变量。
- 保证唯一性,不保证一致性,多个实例共享一个静态变量。
线程
如何创建线程
- 继承Thread类,重写run()方法,调用Thread的start()方法。
- 实现Runnable接口(推荐使用),并实现该结构的run()方法,创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象,调用Thread的start()方法。
- 实现Callable接口,重写call()方法,Callable接口实际是属于Executor框架中的功能类,Callable结构与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要体现在如下三点:
- Callable在任务结束后可以提供一个返回值,Runnable无法提供该功能。
- Callable中的call()方法可以跑出异常,而Runnable中的run()不能跑出异常。
- Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供能了检查计算是否完成的方法。由于线程输入异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下,就可以使用Future来监控目标线程来调用call()方法的情况,当调用Future的get()方法以获取结果时,当前线程会阻塞,直到目标线程的call()方法结束返回结果。
线程的状态-生命周期
- 创建: 通过new关键字创建后,进入到新生状态
- 就绪: 调用start后进入就绪状态
- 运行: CPU调度到本线程后,本线程开始执行。进入到运行状态
- 阻塞: 运行中遇到join,yield,sleep造成阻塞,进入阻塞状态。阻塞完成后,又回到就绪状态
- 终止: 线程正常执行完,或者遇到异常终止后,进入死亡状态
多线程
如何处理多线程数据同步
- synchronized关键字,主要用于synchronize方法和synchronize代码块
- wait()方法与notify()方法
- Lock,JDK5新增加Lock接口以及它的一个实现类ReentrantLock(重入锁),也可以实现多线程的同步;需要自己unlock释放锁
- a. lock():以阻塞的方式获取锁,也就是说,如果获取到了锁,就会执行,其他线程需要等待,unlock()锁后别的线程才能执行,如果别的线程持有锁,当前线程等待,直到获取锁后返回。
- b. tryLock()。以非阻塞的方式获取锁。只是尝试性地去获取一下锁,如果获取到锁,立即返回true,否则,返回false。
- c. tryLock(long timeout,TimeUnit unit)。在给定的时间单元内,获取到了锁返回true,否则false。
- d. lockInterruptibly(). 如果获取了锁,立即返回;如果没有锁,当前线程处于休眠状态,直到获取锁,或者当前线程被中断(会收到InterruptedException异常)。它与lock()方法最大的区别在于如果()方法获取不到锁,就会一直处于阻塞状态,且会忽略Interrupt()方法。
线程池
线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理
线程池的好处
- 降低资源消耗:可以重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控
线程池的工作原理
- 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则执行第二步。
- 线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里进行等待。如果工作队列满了,则执行第三步
- 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略(拒绝策略)来处理这个任务
创建线程池的几种方式(线程池种类)
- 单线程的线程池 (SingleThreadExecutor): 按顺序执行
- 固定大小的线程池(FixedThreadPool):达到最大值时,保持不变,等待老线程结束或异常,新线程代替
- 可缓存的线程池(CachedThreadPool):创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲的线程,当任务数增加时,此线程池又添加新线程来处理任务,可以无限扩大的线程池,比较适合处理执行时间比较小的任务
- 定时线程池(ScheduledThreadPool):支持定时以及周期性执行任务的需求
- JDK8新增(WorkStealingPool):会根据所需的并行级别参数来动态创建和关闭线程,默认使用CPU数量,通过使用多个队列减少竞争,底层用的ForkJoinPool来实现的。支持把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来
说说线程池的拒绝策略
AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作。
CallerRunsPolicy 策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务。
DiscardOleddestPolicy策略: 该策略将丢弃最老的一个请求,并尝试再次提交当前任务。
DiscardPolicy策略:该策略默默的丢弃无法处理的任务,不予任何处理。
自定义策略:实现RejectedExecutionHandler接口