基本概念
1、进程、线程
进程:类似于一个简单的程序
线程:进程里面最小的执行单元,简单来说,线程就是一个程序里不同的执行路径
2、创建线程的方式
2.1、继承Thread类来创建
a.定义一个类继承Thread类,并重写run()方法,run()方法的方法体就是线程具体的实现逻辑,因此把run()方法称为线程的执行体
b.创建该类的实例对象
c.调用线程对象的start()方法启动线程
2.2、实现Runnable接口创建线程
a.定义一个类实现Runnable接口
b.创建该类的实例对象obj
c.将实例对象obj作为构造器参数传入Thread类实例对象,这个对象才是真正的线程对象
d.调用线程对象的start()方法启动线程
继承Thread和实现Runnable接口的区别:
a.实现Runnable接口避免多继承局限(Java只支出单继承,继承Thread类,就不能在继承其它类) b.实现Runnable接口可以更好的体现共享的概念(多个线程执行同一个线程执行类实例,实现数据的共享)
2.3、通过Callable和Future接口创建线程
Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大,call()方法的功能的强大体现在:
a.call()方法可以有返回值
b.call()方法可以声明抛出异常
通过Callable和Future接口创建并启动线程的步骤:
a.创建Callable接口实现类,并实现call()方法,该方法作为线程执行体,且该方法有返回值,再创建Callable实现类的实例
b.使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
c.使用FutureTask对象作为Thread对象的target创建并启动线程
d.调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
2.4、创建匿名线程
2.5、通过线程池启动多线程
a)newFixedThreadPool(int n); 启动一个固定大小的线程池
使用于为了满足资源管理需求而需要限制当前线程数量的场合。使用于负载比较重的服务器
b)newSingleThreadExecutor(): 启动一个单线程的线程池
需要保证顺序执行各个任务的场景
c)newCachedThreadPool(); 启动一个缓存的线程池
当提交任务速度高于线程池中任务处理速度时,缓存线程池会不断的创建线程 适用于提交短期的异步小程序,以及负载较轻的服务器
3、创建线程的几种方式对比
通过继承Thread类实现多线程:
优点: a.实现起来比较简单,而且要获取当前线程,无需调用Thread.currentThread()方法,直接使用this即可获取当前线程;
缺点:
a.线程类已经继承Thread类,就不能再继承其他类
b.多个线程不能共享同一份资源
通过实现Runnable接口或者Callable接口实现多线程:
优点:
a.线程类只是实现了接口,还可以继承其他类
b.多个线程可以使用同一个target对象,适合多个线程处理同一份资源的情况
缺点:
a.通过这种方式实现多线程,相比与第一类方式,编程较复杂
b.要访问当前线程,必须调用Thread.currentThread()方法
注意:多数情况下使用线程池来创建线程
4、常见的线程状态
4.1、Ready就绪状
4.2、Running运行状态
4.3、Teminated结束状态
4.4、TimedWaiting等待
4.5、Waiting等待
4.6、Blocked阻塞
5、synchronized、volatile与CAS
5.1、synchronized(同步锁)
jdk早期,synchronized的底层实现是重量级的,需要到操作系统中去申请锁,效率非常低。
锁升级的概念: 在操作系统中,向内核申请锁,到后期进行了一些改进,HotSpot中的实现是这样的:第一个去访问某把锁的线程,比如sync(Object),先在这个Object的头上面markword记录这个线程。(如果只有第一个线程访问的时候实际上是没有给这个Object加锁的,在内部实现的时候,只是记录这个线程的ID(偏向锁))。
偏向锁如果有线程竞争,就升级为自旋锁。
当自旋锁转圈十次之后,将升级为重量级锁,重量级锁就是去操作系统那里去申请资源,这是一个锁升级的过程。
执行时间短(加锁代码),线程数少,用自旋
执行时间长,线程数多,用系统锁(重量级锁)
锁升级的四种状态:无锁、偏向锁、轻量级锁、重量级锁
5.2、volatile
作用:
1、保证线程的可见性
2、禁止指令重排序
synchronized和volatile知识回顾: synchronized锁的是对象而不是代码,锁方法锁的是this,锁static方法锁的是class,锁定方法和非锁定方法是可以同时执行的,锁升级从偏向锁到自旋锁到重量级锁 volatile保证线程的可见性,同时防止指令重排序。线程可见性在CPU的级别是用缓存一致性来保证的;禁止指令重排序CPU级别是禁止不了的,是内部运行的过程,提高效率的。添加volatile之后,指令重排序就可以禁止。
5.3、CAS
CAS(compareAndSet) 无锁优化,或者叫自旋。
CAS会产生ABA问题
JUC同步工具
1、synchronized和ReentrantLock的不同?
synchronized:系统自带、系统自动加锁,自动解锁,不可以出现多个不同的等待队列,默认进行四种锁状态的升级
ReentrantLock:需要手动加锁,手动解锁,可以出现多个不同的等待队列,CAS的实现
线程池
线程池7大参数:
1、corePoolSize 核心线程数
2、maxinumPoolSize 最大线程数
3、keepAliveTime 生存时间
4、TimeUnit.SECOUNDS 生存时间的单位
5、BlockingQueue任务队列
6、defaultThreadFactory 线程工厂
7、拒绝策略,指的是线程池忙,并且在任务队列满的情况下,执行设置的拒绝策略,JDK默认提供了4种拒绝策略,也可以自定义
JDK默认提供的拒绝策略:
Abort:新建的任务抛异常
Discard:扔掉新建的任务,不抛异常
DiscardOldSet:扔掉排队时间最久的旧任务,尝试提交新的任务
CallerRuns:不抛弃新任务,也不抛异常,将新建的任务退回到调用者,由调用者自己执行
线程池的大小=CPU的数目*期望的CPU *(1+等待时间/计算时间)