轻松拿捏多线程与高并发(一)

线程池核心线程数和最大线程数如何设置?

核心线程数:线程池中常驻的线程个数
max线程数:线程池中可以开辟的最大线程池个数
如何设置:
看是CPU密集型任务|IO密集型任务|混合型任务?
看是核心业务还是非核心业务?
看压测的结果?
用jvisual+apifox来计算?

cpu密集型:比如找1-100000中的素数
io密集型:比如文件io,网络io

分别都有公式来计算对应,不过最合适的方法就是压测(因为理想情况下,肯定会有很多线程来影响,比如java的垃圾回收等等)

tomcat默认线程为200(除非自己去设置线程)

压测(apifox),自己设置一次性压测多少数量(去写一个接口,然后去压测这个接口),然后去判断,自己设置的不同线程分别用多少时间

线程池中提交一个任务的流程?

execute提交一个线程对象(Runnable)——判断当前线程池的线程数(核心线程数)(满或者没满)——对比工作队列(入队还是不入队)——用当前线程数对比最大线程数(满了还是没满)

线程池几种状态?怎么变化?

均与是否接受新任务|是否处理队列任务有关
Running 会 会
shoutdown 不会 会 且任务处理完后会中断所有线程
stop 不会 不会 直接中断所有线程
tidying 所有线程都停止后,才转状态,然后调用terminated()方法(这个方法是空方法,自己可以去扩展)
terminated terminated()方法结束后转变为TERMINATED状态

如何停止一个线程?

Thread有两个方法:
start():开启一个线程
stop():停止一个线程(但是很粗暴,会直接停掉线程,会破坏synchronize的锁,但是不会破坏reentrantlock)

java并发中的原子性

原子性需要通过各种锁机制来保证
原子性是指在多线程并发环境下,保证数据的一致性,避免对同一个变量出现数据竞争和脏数据(即一组代码要么完全执行成功,要么完全不执行。不会出现执行到一半被其他线程打断或干扰的情况)

为什么会发生这种情况?
因为我们知道cpu、内存、io(磁盘、网络)之间的性能差异,所以为了提高cpu的利用率,线程执行io操作的时候,会让出cpu,然后cpu去执行其他线程的指令,cpu根据固定的时间片来切换执行不同线程,然后就会导致某个线程对一个变量执行的时候,还没有执行完,就被另一个线程拿去操作了,然后这个变量发生改变,这个线程执行后,然后重新拿回去给最初线程操作,相当于二者发生了对同一个变量的数据竞争,所以为了防止这种变量在不同线程出现不同情况,我们让操作同一变量的操作变成原子性。

主要还是因为:每个线程去修改一个变量后,会把修改的值缓存到自己的cpu里边,然后拿到时间片后,读也是读自己cpu缓存的变量值,才会导致不同线程对与同一个变量有不同的值。

也就是本地cpu缓存导致的

有序性

就是多线程并发执行时,可能会发生指令的重排序(编译器为了编译优化,进行指令重排序),导致程序的执行顺序和预期不一样,然后出现数据竞争和线程安全的问题。
因此,我们要让并发变成有序性,让多线程执行的指令和操作,按照开发者编写的顺序进行执行

ps:可能会在单例模式出现问题,比如要先对某个对象进行判断是否存在,然后才能拿到锁,进行相关操作,A线程先拿到锁,然后初始化对象,结果编译器优化的顺序把我们代码的顺序改变了,B线程直接就可以跳过判断条件了

用锁机制、volatile来保证

可见性

多线程并发访问共享变量时,对共享变量的修改结果能够立刻被其他线程获取

a线程读取变量i,从内存拉到自己cpu里去修改,然后缓存一份在自己cpu里边,但是还没写回内存,结果b就来了,然后就导致a和b两个虽然是获取同个变量i,但是结果是不同的,所以这就是可见性问题,加了volatile就能保证内存能及时获取被拉到别人线程修改的变量值。

用volatile来保证

可见性是由于写回内存的延迟导致的

创建线程的几种方式

继承Tread类(单继承,继承了这个,就不能继承别的类了)
public class xx extend Thread

public class Thread implements Runnable

Thread类底层也是实现Runnable来实现的

实现Runnbale接口(不光实现接口了,还能继承别的类)
方法:
1.直接 Thread thread = new Thread(new xx类());
2.匿名内部类方法
Thread thread = newThread(newRunnable());

实现Callable接口
不同点在于,可以开启一个线程去执行任务,然后拿到执行任务的结果,要结合FutureTask

创建一个FutureTask<>(),然后去创建一个线程,把FutureTask传进去,开启线程,然后可以调用futuretask的方法,获取这个线程的结果(结果在call方法里边,call方法是要去重写的方法,我们把想要执行的放在这里去执行,然后futuretask的方法可以获取这里的值)


public class FutureTask<V> implements RunnableFuture<V>

线程池来创建
Executors(不建议使用)
工作中使用ExecutorService来创建线程

为什么不建议使用Executors来创建?

Synchronized和ReentrantLock

synchronized是关键字 自动加锁|释放锁 非公平锁 jvm层面的锁 锁能升级(偏向锁 轻量级锁 重量级锁 )
ReentrantLock是jdk的一个类 手动加|释放 公平锁或非公平锁 api层面的锁 锁不能升级

ThreadLocal

1.使用场景
java提供的本地存储机制,可以就数据缓存在某个线程内部,然后这个线程可以在任何时候,任何方法中获取到缓存的数据

2.底层实现
底层是ThreadLocalMap,map的键为ThreadLocal对象,值为要缓存的值

3.内存泄漏(数据占用了这块内存,但是不被使用,自己不用,别人也用不了)
使用ThreadLocal对象使用完后,要记得把存进去的对象回收,需要手动调用ThreadLocal的remove方法。因为存进去ThreadMap是通过强引用指向对象,线程不会回收,因此我们需要手动清除存进去的对象。

4.应用场景:一个线程持有一个ThreadLocalMap,然后键是ThreadLocal,值为自己存入的数据(线程之间不共享同一个连接)

ReentrantLock(可重入,公平和非公平都是可重入)

1.分为公平锁和非公平锁
这两个锁底层均是AQS来进行排队区别在于线程使用lock()方法加锁
公平锁检查AQS队列里是否有线程在排队,有线程排队,当前线程也去排队(有道德懂排队
非公平锁:不去检查是否有线程排队,直接竞争锁(毫无道德底线),如果两者都没竞争到锁,都会排队

但是lock方法释放锁的时候:
释放的时候都会唤醒最前边的线程**

公平和非公平差距只在加锁阶段

Synchronized锁升级的过程

偏向锁:记录一下获得到这个锁的线程id,可以让它下一次再次要来获取锁的时候直接获得到,即支持锁重入

轻量级锁:偏向锁升级,一个线程已经拿到这把锁,另外线程来竞争,就升级了,底层是自旋(CAS)实现,不会阻塞线程

重量级锁:自旋次数过多还没获得锁,就升级了,重量级锁会阻塞线程

自旋锁:线程通过CAS获取预期的一个标记,没有获取到,就继续循环获取,获取到了这个标记,就代表获取到了锁,这个期间不会影响线程的正常运行,所以很轻量,也不会过多调用操作系统的资源

对象头

对象=对象头 + 实例数据 + 对齐填充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值