什么是并发和并行
并行:两个和多个时间在同一时刻发生
并发:两个或多个时间在同一时间段发生
什么是进程、线程
进程是资源调度的基本单位
线程是CPU调度的基本单位
线程是进程内部的一个独立执行单元
一个进程可以有多个线程
线程同步的方式
- 同步代码块
- 同步方法
- 同步锁
- 特殊域变量
- 局部变量
- 阻塞队列
- 原子变量
同步代码块和同步方法
同步代码块自选定锁定的对象;
静态同步方法锁的是this,非静态同步方法锁的是字节码对象
进程和线程
进程是资源调度的基本单位
线程是CPU调度的基本单位
线程是进程内部的一个独立执行单元
一个进程可以有多个线程
Java中线程的四种创建方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 线程池
线程池
线程池关系图
创建线程:Thread方式和Runnable方式比较
- 接口更适合多个相同的程序代码的线程去共享同一个资源
- 接口可以避免单继承的局限性
- 接口代码可以被多个线程共享,代码和线程独立
- 线程池只能放入Runable或Callable的实现类
创建线程:Runnable方式和Callable方式比较
共同点:都是接口都可以用来写多线程,都需要Thread.start()
不同点:
- Callable可以返回值,Runable不行。
- Callable的call()方法可以抛出异常:Runnable的run()方法不行。
- Callable可以调用Future.cancel取消执行,Runnable不行。
线程的生命周期
https://blog.csdn.net/weixin_43978695/article/details/109020758
Java语言定义了6种线程状态,在任意一个时间点中,一个线程只能有且只有其中一种状态,并且可以通过特定的方法在不同状态之间转换。这6种状态包含
- 新建(New):创建后尚未启动的线程处于这种状态。
- 运行(Runnable):包括操作系统线程状态中的Runnning和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着操作系统为它分配执行时间。
- 无限期等待(Waiting):处于这种状态的线程不会被分配处理器执行时间,它们要等待被其他线程显式唤醒。以下方法会让线程陷入无限期等待状态。
- 没有设置Timeout参数的Object::wait()方法
- 没有设置Timeout参数的Thread::join()方法
- LockSupport::park()方法。
- 限期等待(Timed Waiting):处于这种状态的线程也不会被分配处理器执行时间,不过无须等待被其他线程显式唤醒,在一定时间之后它们会由系统自动唤醒。一下方法会让线程进入限期等待状态:
- Thread::sleep()方法;
- 设置了Timeout参数的Object::wait()方法;
- 设置了Timeout参数的Thread::join()方法;
- LockSupport::parkNanos()方法;
- LockSupport::parkUntil()方法。
- 阻塞(Blocked):线程被阻塞了,“阻塞状态”与“等待状态”的区别是“阻塞状态”在等待着获取到一个排它锁,这个时间将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程进入这种状态。
- 结束(Terminated):已终止线程的线程状态,线程已经结束执行。
线程同步的方式
https://blog.csdn.net/weixin_39214481/article/details
- 同步方法
- 同步代码块
- 同步锁
- volatile关键字
- ThreadLocal局部变量
- 阻塞队列
- 原子变量
同步代码块
将需要同步的代码外部加上synchronized关键字,并且指定锁定的对象(指定synchronized后的参数)
同步方法
用synchronized关键字修饰方法
当方法是非静态的同步锁锁的是this(保护整个方法)
当方法是静态的同步锁锁的是字节码对象(类名.class)(会锁住整个类)
同步锁
自Java5后,新增了java.util.concurrent包支持同步
ThreadLocal
线程变量,ThreadLocal中田中的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的变量。
synchronized关键字和lock类对比
都是可重入锁(可重入:某个线程已经获得某个锁,可以再次获取锁而不会死锁)
synchronized是一个关键字;lock在jvm层面是个类
synchronized关键字无法判断是否获取到锁,lock可以
synchronized执行完自动释放,lock的锁需要手动释放
synchronized关键字会阻塞线程,lock支持线程的等待可中断
synchronized关键字不支持公平锁,lock类支持公平锁
线程通讯
休眠唤醒方式:
- Object的的wait(),notify(),notifyAll()
- Condition的await(),signal(),signalAll()
- CountDownLatch:用于某个线程等待若干个其他线程执行后,它才执行
- CyclicBarrier:一组线程等待至某个状态之后再全部执行
- Semaphore:用于控制对某组资源的访问权限
Java内存模型
参考链接:
工作过程
- 由javac将.java文件编译成.class文件
- 类加载器加载字节码文件到方法区
- 执行引擎从方法区找到Main方法
- 为方法创建栈帧放入方法栈,同时创建该栈帧的程序计数器
- 执行引擎请求CPU执行该方法
- CPU将方法栈数据加载到工作内存(寄存器和高速缓存),执行该方法
- CPU执行完之后将执行结果从工作内存同步到主内存
多线程编程保证满足的三个特性
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:如果在本线程内观察,所有的操作都是有序的;如果在另一个线程中观察一个线程,所有的操作都是无序的。
ThreadLocal
原子类
Lock类
Volatile关键字
线程池
多线程的缺点:处理任务的线程创建和销毁都十分耗时并消耗资源。
多线程之间的切换也会非常耗时并消耗资源
解决方法:采用线程池
使用时线程池已经存在,消除了线程创建的时耗。
通过设置线程数目,防止资源不足。
ThreadPoolExecutor
线程池类
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue< Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize:核心线程数
maximumPoolSize:线程池中能用的最多线程数
workQueue:用于缓存任务的阻塞队列。可以为下面两种参数
- SynchronousQueue< Runnable>:不缓存任何一个任务;向线程池提交任务时,如果没有空闲线程来运行任务,则入列操作会阻塞。有线程来获取任务时,出列操作唤醒入列操作(最大线程数参数设置无效化了)(无界队列)
- LinkedBlockingQueue< Runnable>:链表实现的队列,可以有界,也可以无界,在Executors中默认使用无界的。
上面三个参数的关系为:
- 如果没有空闲的线程执行该任务且当前运行的线程数少于corePoolSize,则添加新的线程执行该任务。
- 如果没有空闲的线程执行该任务且当前的线程数等于corePoolSize同时阻塞队列未满,则将任务入队列,而不添加新的线程。
- 如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数小于maximumPoolSize,则创建新的线程执行任务。
- 如果没有空闲的线程执行该任务且阻塞队列已满同时池中的线程数等于maximumPoolSize,则根据构造函数中的handler指定的策略来拒绝新的任务。
KAT:空闲线程的存活时间
unit:KAT的单位
handler:workQueue满,线程数达到最大线程数时对新加入任务的处理策略
- ThreadPoolExecutor.AbortPolicy():抛出RejectedExecutionException异常
- ThreadPoolExecutor.callerRunsPolicy():由向线程池提交任务的线程来执行该任务
- ThreadPoolExecutor.DiscardOldPolicy():抛弃最旧的任务
- ThreadPoolExecutor.DiscardPolicy():抛弃当前任务
四种常用线程池
Executors类创建
- newCachedThreadPool
- newFixedThreadPool
- newSingleThreadExecutor
- newScheduleThreadPool
.nCTP:可缓存线程池,线程池长度超过处理需要,可以灵活回收空闲线程,若无可回收,则新建线程。
.nFTP:创建一个指定工作线程数量的线程池,提交一个任务就创建一个工作线程,工作线程数量等于线程池初始的最大数时,将提交的任务存入到池队列当中。
优点在于提高程序效率,节省创建线程时的开销。
缺点在于没可运行任务时,不释放工作线程,占资源。
.nSTE:单线程化的Executor,只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。
单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会又多个线程是活动的。
.nSTP:该方法创建一个定长的线程池,而且支持定时以及周期性的任务执行(按时间片调度线程)。
SpringBoot使用多线程
- 配置一个类型为 java.util.concurrent.TaskExecutor或其子类的 bean
- 在配置类或直接在程序入口类上声明注解 @EnableAsync
- Spring管理的对象的方法上标注注解 @Async