Java基础_线程
进程:
进程是具有一定独立功能的程序对于数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位;进程在执行过程中拥有独立的内存单元;
链接: https://mp.weixin.qq.com/s/KOZytR49edcP564ekTQMzA
线程:
定义 | 线程是进程的一个实体,是CUP调度和分配的基本单位; 同一个进程中的多个线程可以并发运行; 多个线程共享内存,从而极大地提高了程序的运行效率; 线程不分主次,主函数也是一个线程; |
生命周期 | 1,New:新建创建Thread类的一个实例,线程被创建,但未启动; 2,Runnable:就绪,线程已经启动,等到CPU资源; 3,Running:启动,线程已经获取到CPU资源,正在执行任务; 4,Blocked:阻塞,正在运行的线程让出CPU资源并暂停执行; 5,Dead:死亡,线程执行完成或者被其他线程杀死; |
特性 | 1,原子性:保证数据一致; 2,有序性:保证线程之间顺序执行; 3,可见性:对其他线程可见; |
说明 | 启动线程,是调用线程的start()方法,此时线程进入可执行状态; run()方法只是thread的一个普通方法调用,调用线程的run()方法还是在主线程里执行;调用线程的start()方法会启动线程然后自动调用 run ()方法 |
线程实现之继承Thread类:
每个线程都独立,不共享资源;
代码路径:E:\IDEA_Study\Scattered_Study\Study\src\线程\ThreadTest.java
线程实现之实现Runnable接口:
每个线程共享了对象资源;
代码路径:E:\IDEA_Study\Scattered_Study\Study\src\线程\RunnableTest.java
线程池:
作用: 减少创建和销毁线程的次数,工作线程可以重复使用;可以根据系统承受能力调整线程池中执行线程的数量,防止消耗过多的内存;
工作流程: 当有新任务提交到线程池,判断是否达到corePoolSize,如果没有达到,创建一个新线程,并将该任务作为该线程的第一个任务,如果达到,则放入缓存队列;如果缓存队列已满,判断是否达到maximumPoolSize,如果没有达到,则创建创建非核心线程执行任务;如果达到,则采用线程拒绝策略处理;
代码路径:E:\IDEA_Study\Scattered_Study\Study\src\线程\ThreadPoolTest.java
链接: https://blog.csdn.net/HepBen/article/details/80088719
线程池创建方式:
Executors.newFixedThreadPool | 创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待; 可能导致内存溢出 |
Executors.newCachedThreadPool | 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程; 可能导致CPU达到100% |
Executors.newSingleThreadExecutor | 创建单个线程数的线程池,它可以保证先进先出的执行顺序; 可能导致内存溢出 |
Executors.newScheduledThreadPool | 创建一个可以执行延迟任务的线程池; |
Executors.newSingleThreadScheduledExecutor | 创建一个单线程的可以执行延迟任务的线程池; |
Executors.newWorkStealingPool | 创建一个抢占式执行的线程池(任务执行顺序不确定);【JDK 1.8 添加】 |
newCachedThreadPool:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newFixedThreadPool:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newSingleThreadExecutor:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
线程池的核心参数:
public ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//线程存活时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//线程池队列,存放待执行任务
ThreadFactory threadFactory,//线程工程,线程在这里生成
RejectedExecutionHandler handler//拒绝策略,当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理 ) {
...
线程池的workQueue参数:
ArrayBlockingQueue | 一个由数组结构组成的有界阻塞队列 |
LinkedBlockingQueue | 一个由链表结构组成的有界阻塞队列 |
SynchronousQueue | 一个不存储元素的阻塞队列,即直接提交给线程不保持它们 |
PriorityBlockingQueue | 一个支持优先级排序的无界阻塞队列 |
DelayQueue | 一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素 |
LinkedTransferQueue | 一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法 |
LinkedBlockingDeque | 一个由链表结构组成的双向阻塞队列 |
线程池拒绝策略:
AbortPolicy(默认) | 丢弃任务并抛出RejectedExecutionException异常 | 针对比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现 |
DiscardPolicy | 丢弃任务,但是不抛出异常 | 一些无关紧要的业务建议采用此策略 |
DiscardOldestPolicy | 抛弃队列头部(最旧)的一个任务,并执行当前任务 | |
CallerRunsPolicy | 使用当前调用的线程来执行此任务 |
注:如果我们想要根据实际业务场景需要,设置其他的线程池拒绝策略,可以通过ThreadPoolExecutor重载的构造方法进行设置;
线程池的状态:
RUNNING | 运行状态,该状态下线程池可以接受新的任务,也可以处理阻塞队列中的任务 执行 shutdown 方法可进入 SHUTDOWN 状态 执行 shutdownNow 方法可进入 STOP 状态 |
SHUTDOWN | 待关闭状态 ,不再接受新的任务,继续处理阻塞队列中的任务 当阻塞队列中的任务为空,并且工作线程数为0时,进入 TIDYING 状态 |
STOP | 停止状态,不接收新任务,也不处理阻塞队列中的任务,并且会尝试结束执行中的任务 当工作线程数为0时,进入 TIDYING 状态 |
TIDYING | 整理状态,此时任务都已经执行完毕,并且也没有工作线程 执行 terminated 方法后进入 TERMINATED 状态 |
TERMINATED | 终止状态,此时线程池完全终止了,并完成了所有资源的释放 |
线程池的原理:
execute方法:
execute方法源码分析:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* clt记录着runState和workerCount
*/
int c = ctl.get();
/*
* workerCountOf方法取出低29位的值,表示当前活动的线程数;
* 如果当前活动线程数小于corePoolSize,则新建一个线程放入线程池中;
* 并把任务添加到该线程中。
*/
if (workerCountOf(c) < corePoolSize) {
/*
* addWorker中的第二个参数表示限制添加线程的数量是根据corePoolSize来判断还是maximumPoolSize来判断;
* 如果为true,根据corePoolSize来判断;
* 如果为false,则根据maximumPoolSize来判断
*/
if (addWorker(command, true))
return;
/*
* 如果添加失败,则重新获取ctl值
*/
c = ctl.get();
}
/*
* 如果当前线程池是运行状态并且任务添加到队列成功
*/
if (isRunning(c) && workQueue.offer(command)) {
// 重新获取ctl值
int recheck = ctl.get();
// 再次判断线程池的运行状态,如果不是运行状态,由于之前已经把command添加到workQueue中了,
// 这时需要移除该command
// 执行过后通过handler使用拒绝策略对该任务进行处理,整个方法返回
if (! isRunning(recheck) && remove(command))
reject(command);
/*
* 获取线程池中的有效线程数,如果数量是0,则执行addWorker方法
* 这里传入的参数表示:
* 1. 第一个参数为null,表示在线程池中创建一个线程,但不去启动;
* 2. 第二个参数为false,将线程池的有限线程数量的上限设置为maximumPoolSize,添加线程时根据maximumPoolSize来判断;
* 如果判断workerCount大于0,则直接返回,在workQueue中新增的command会在将来的某个时刻被执行。
*/
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*
* 如果执行到这里,有两种情况:
* 1. 线程池已经不是RUNNING状态;
* 2. 线程池是RUNNING状态,但workerCount >= corePoolSize并且workQueue已满。
* 这时,再次调用addWorker方法,但第二个参数传入为false,将线程池的有限线程数量的上限设置为maximumPoolSize;
* 如果失败则拒绝该任务
*/
else if (!addWorker(command, false))
reject(command);
}
简单来说,在执行execute()方法时如果状态一直是RUNNING时,的执行过程如下:
1,如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务;
2,如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中;
3,如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务;
4,如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
这里要注意一下addWorker(null, false);,也就是创建一个线程,但并没有传入任务,因为任务已经被添加到workQueue中了,所以worker在执行的时候,会直接从workQueue中获取任务。所以,在workerCountOf(recheck) == 0时执行addWorker(null, false);也是为了保证线程池在RUNNING状态下必须要有一个线程来执行任务。
execute方法执行流程:
sleep和wait的区别:
sleep: 让出资源,但监控状态依然保持,指定时间到了之后自动恢复,即sleep过程中线程没有释放对象锁;sleep需要抛出异常;sleep是Thread类方法;Sleep可以在任何地方使用;
wait: 会释放对象锁,让其他线程可以获取对象锁,线程wait后必须等待其他线程调用notify或notifyAll方法唤醒该线程;wait是Object方法;wait只能在synchronized块或synchronized方法中使用;
并行和并发的区别:
并行: 多个任务同时执行;
并发: 根据虚拟机分配的时间片分时间运行不同的任务,同一时间只有一个任务在进行;
停止线程的方式:
1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止;
2、使用stop方法强制终止线程(不推荐使用);
3、使用interrupt方法中断线程,使线程处于阻塞状态;
守护线程:
Java的线程分为两种:User Thread(用户线程)、DaemonThread(守护线程);
当进程不存在或者主线程停止时,守护线程也会停止;使用setDaemon(true)方法设置为守护线程;
join方法:
让其他线程变为等待,只有当前线程执行完成后,等待的线程才会被释放;
Volatile关键字:
使变量在多个线程之间可见;volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性;
synchronized关键字:
可以修饰代码块,方法,但是不能修饰构造器,成员变量;
任何线程进入同步代码块,同步方法之前,必须先获取对同步监视器的锁定;
volatile和synchronized的区别:
volatile修饰的变量不保留拷贝,直接访问主内存中的;
synchronized修饰的方法或者代码块时,能保留同一时刻最多一个线程访问该代码;
链接: https://blog.csdn.net/suifeng3051/article/details/52611233
Synchronized和Lock的区别:
Lock能完成Sychronized的全部功能;有更精确的线程语义和更好的性能,不强制要求一定要获取锁;
Sychronized会自动释放锁,但是Lock需要程序员手动释放,最好在finally中释放;
共享内存模型:
Java内存模型: 简称JMM,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化;
ThreadLocal:
ThreadLocal: 提供线程内的局部变量,为变量在每个线程中都创建了一个副本,这种变量在多线程环境下访问时能够保证各个线程里变量的独立性;
信号量:
Semaphore: 用来保护一个或者多个共享资源的访问,Semaphore内部维护了一个计数器,其值为可以访问的共享资源的个数。一个线程要访问共享资源,先获得信号量,如果信号量的计数器值大于1,意味着有共享资源可以访问,则使其计数器值减去1,再访问共享资源。如果计数器值为0,线程进入休眠。当某个线程使用完共享资源后,释放信号量,并将信号量内部的计数器加1,之前进入休眠的线程将被唤醒并再次试图获得信号量;
临界资源:
多个线程共享的资源;
同步代码块:
使用synchronized将run()方法里面的方法修改为同步代码块,同步监视器就是account对象,符合"加锁-修改-解锁"的逻辑,保证了并发线程中任一时刻只有一个线程在修改共享资源的代码区;
链接: https://www.cnblogs.com/lyy-2016/p/6264960.html
死锁:
线程之间资源相互等待;
代码路径:E:\IDEA_Study\Scattered_Study\Study\src\线程\DeadLockTest.java
释放同步监视器:
线程执行同步代码块或同步方法时,程序调用Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器;线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器;
生产者与消费者设计模式:
代码路径:E:\IDEA_Study\Scattered_Study\Study\src\线程\StorqgeTest.java
链接: https://www.cnblogs.com/Ming8006/p/7243858.html