概述
1.线程和进程
进程
进程是资源分配的基本单位,是程序执行时的一个实例,程序运行时系统就会创建一个进程,也就是说你的电脑每开启一个程序,就产生一个进程,然后这个进程会放入就绪队列,进程调度器选中时会进行分配cpu的时间,程序进行运行
线程
是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
2.创建线程的方式
A-继承Thread类
先让一个类进行继承Thread类,在里面进行run方法的编写,在进行新建该类进行start的启动
B-实现Runnable接口
先进行一个类进行实现接口runnable,复写里面的方法fun
在进行新建一个实现类,在把这个实现类给放在新建的thread类进行start方法进行启动
C-实现Callable接口
创建一个类进行实现Callable接口,覆写call方法,把新建的类callable类放到新建的FutureTask类里面,再把callable放到新建的Thread类里面在进行start进行启动
D-线程池-(这里是提供的固定的长度的线程)
private ExecutorService executorService = Executors.newFixedThreadPool(5);
public static void main(String[] args) {
executorService.execute(()->{
System.out.println("线程池创建的线程");
});
}
3.线程池的详细介绍
3.1线程池的作用
A-控制并发数量:线程并发数量过多,抢占系统资源从而导致阻塞,线程池可以限制线程的数量
B-线程的复用:创建/销毁线程伴随着系统开销。过于频繁的创建/销毁线程,会很大程度上影响处理效果和速度
C-管理线程的生命周期:对线程进行一些简单的管理,创建,销毁等
3.2线程池的执行流程
核心线程>=等待队列>=非核心线程>=拒绝策略
核心线程:一个线程池里面的核心主要的线程核心数
等待队列:当任务来了,但是线程池里面的核心线程都在进行工作,那就把这个任务放到等待队列里面进行排队,等待核心线程空出去执行任务
非核心线程:当等待队列里面的任务过多,就会开启非核心线程去处理任务,但是非核心线程数和核心线程数之和不能超过最大线程数
拒接策略:当非核心线程数和核心线程数之和超过最大线程数,1.线程丢弃正在处理的任务去处理队列里面的任务,2.交给main主线程处理,3.不处理
注意:
1.如果有空闲的线程直接进行使用,没有空闲的线程并线程的数量没有达到corePoolSize,则新建一个线程(核心线程)去执行任务
2.当线程的数量达到了corePoolSize,就把这个任务放到队列去,通过getTask方法进行去拿到队列里面的任务,如果队列里面没有任务,getTaask将会进入阻塞状态,直到获取到任务,期间不会对cup进行资源的消耗。
3.3线程池的七大核心参数
- corePoolSize:核心线程数。
- maximumPoolSize:最大线程数。
- keepAliveTime:空闲线程存活时间,当非线程空闲的时间达到这个时间时会进行销毁
- TimeUnit:空闲时间单位
- BlockingQueue:线程池任务队列。
- ThreadFactory:创建线程的工厂
- RejectedExecutionHandler:拒绝策略 AbortPolicy丢弃任务并跑出异常,DiscardPolicy丢弃任务但是我不告诉你,DiscardOldestPolicy我会把队列里面最前面的任务丢掉,CallerRunsPolicy 叫我老大main主线程来处理
3.4jdk自带的线程池
1.CachedThreadPool 可缓存的
2.FixedThreadPool 固定长度的
3.SingleThreadPool 单一的
4.ScheduledThreadPool 可调度的
四大线程的对比图
自带的线程池都可通过Executor来进行创建
3.5 DIY线程池
为什么有了自带的线程池还有进行自己DIY线程池?
阿里不推荐使用Executors创建线程池
1.FixedThreadPool和SingleThreadExecutor => 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而引起OOM异常
2.CachedThreadPool => 允许创建的线程数为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
public class MyThreadFactory {
public static void main(String[] args) {
new ThreadPoolExecutor(4,//核心数量4个
9,//最大线程不超过9个
2L,//2分钟去销毁空闲的非核心的线程
TimeUnit.MINUTES,//单位分
new ArrayBlockingQueue<Runnable>(9),//等待队列 最大等9个
new ThreadPoolExecutor.AbortPolicy()//任务满了我就丢了再给你抛异常
);
}
}
4 线程安全-锁
1.线程安全问题
就是 多线程环境中 , 且存在数据共享 , 一个线程访问的共享 数据被其他线程修改了, 那么就发生了线程安全问题 , 整个访问过程中 , 无一共享的数据被其他线程修改了 就是线程安全的
程序中如果使用成员变量, 且对成员变量进行数据修改 , 就存在数据共享问题, 也就是线程安全问题
2.锁
锁是来进行解决线程安全的问题,一般分为悲观和乐观锁,
悲观锁:顾名思义,就是认为每次拿数据都会出现问题,那就在那数据之前进行锁上,下一个拿数据等锁释放之后在进行拿数据,比如synchronized锁
乐观锁:认为每次拿数据都不会出现问题,所以当拿数据出现问题之后才进行处理上锁,基于cas锁
3.第一种锁sychronized锁
sychronized锁是一个java的关键字,是jvm给我们进行实现了的,它的主要的实现的一个原理是jvm内置锁有一个对象头监视器对对象进行监视,说这个对象有锁了,那它的对象头上就会有一个标记,那其他的线程就不能进行操作
4.关于sychronized锁的膨胀机制或升级机制
A-先是偏向锁就是说这个有一个线程进行操作,下次这个线程又来了就还是给这个线程进行操作
B-来了一个线程多个线程那就进行轻量级锁,这个后来的锁进行一个自旋,进行不断判断上一个线程是否完成了操作
C-重量级锁,这个是为了避免忙等的,当这个线程进行自旋10次就进行不在等进行线程的放回
5.第二种锁lock
lock是一个接口,jdk提供的一个接口是juc并发库里面的,这个是要我们进行手动释放锁的(就是unlock),而sychronized是不需要的
6.第三把锁 原子类(其实不太算是锁,但可以解决线程安全问题)
原子类是juc下面的一个包atomic类,它里面是我们封装了不同类型封装的原子类,我们直接拿来用即可,
所以我的大致解决线程问题的思路就是:要不你就加锁,那线程一个一个排位来进行操作。要不就解决原子性(只有一个)
7.关于出现的CAS和ABA问题
CAS:对比并更新
大致意思是:有三大东西,U是这个变量的值是否是一致的,A是预期值 ,B更新值
先进行对比,看这个A和U的值是否是一致的,只有是满足时才进行更新内存的值为B值
ABA:就是有一个变量的值是10的时候,A进行添加操作进行+1的操作,而B线程来了之后进行-1的操作这样下来这个值还是10,当C来进行操作的时候,它吧这个10拿出来不是之前A操作的时候10那个值了,所以就出现了ABA的问题
它的解决方案是进行添加一个版本号进行区分,比如 ElasticSearch
关于详细的CAS问题可参考下面文章
https://blog.csdn.net/bjweimengshu/article/details/78949435?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165880780516782246423613%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=165880780516782246423613&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-78949435-null-null.185^v2^control&utm_term=CAS&spm=1018.2226.3001.4450
欢迎进行学习交流,不足之处请指出,喜欢麻烦点赞+收藏,谢谢各位大佬了
最后
以上就是粗犷航空为你收集整理的一篇让你掌握线程和线程池,还解决了线程安全问题,确定不看看?的全部内容,希望文章能够帮你解决一篇让你掌握线程和线程池,还解决了线程安全问题,确定不看看?所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复