一、线程的上下文切换
前提:一个CPU的内核一个时间只能运行一个线程中的一个指令
线程并发:CPU内核会在多个线程间来回切换运行,切换速度非常的快,达到同时运行的效果
线程切换回来后,如何从上次执行的指令后继续执行?
程序计数器(每个线程都有属于自己的独立计数器,用于记录上次执行的行数)
线程执行会随时切换,如何保证重要的指令能完全完成?
线程安全问题
CPU进行上下文切换的过程中性能会降低
二、线程的安全(同步)问题
CPU在多个线程间切换,可能会导致某些重要的指令不能完整执行,出现数据错误的问题
出现线程安全问题的三个条件:
- 多个线程
- 同一时间
- 执行同一段指令或修改同一变量
三、线程安全问题的解决办法
解决方法:给程序上锁,让当前线程完整执行一段指令,执行完后释放锁,其他线程再执行
几种上锁方式:
- 同步方法
- 同步代码块
- 同步锁
同步方法
给方法添加synchronize关键字
会给整个方法上锁
过程:
当前线程调用方法后,方法上锁,其他线程无法执行,调用结束后释放锁
锁对象:
非静态方法 this
静态方法 当前类.class
锁对象,可以对当前线程进行控制,如:wait等待、notify通知
任何非局部变量的对象都可以作为锁
同步代码块
粒度比同步方法小(粒度越小越灵活,性能更高)
给一段代码上锁
synchronized(锁对象){
代码
}
同步锁
Lock接口
基本方法:
lock() 上锁
unlock()释放锁
常见实现类
- ReentrantLock 重入锁
- WriteLock 写锁
- ReadLock 读锁
- ReadWriteLock 读写锁
使用方法:
1.定义同步锁对象
2.上锁
3.释放锁(最后一定要释放锁,否则线程无法释放,会导致其他线程无法进入)
Lock lock = new ReentrantLock();
//方法内部上锁
lock.lock();
try{
代码
}finally {
//释放锁
lock.unlock();
}
三种锁对比:
粒度:
同步代码块/同步锁 < 同步方法
四、悲观锁和乐观锁
悲观锁
对外界修改数据持保守状态,认为线程的安全问题容易出现,会对代码上锁
前面的锁机制都属于悲观锁
悲观锁的锁定和释放要消耗比较多的资源,降低程序的性能
乐观锁
采取了更宽松的上锁机制,认为线程的安全问题并不常见,不会刻意对代码上锁
两种实现方式:
- 版本号机制
利用版本号记录数据更新的次数,一旦更新,版本号+1,线程修改数据后会判断版本号是否是已更新的次数,如果不是就不更新数据
- CAS(Compare And Swap)比较和交换算法
通过内存的偏移量获得数据的值
计算出一个预计的值
将提交的实际值和与机制进行比较,如果相同,执行修改,如果不同就不改
悲观锁更重量级,占用资源更多,应用于线程竞争比较频繁的情况,多写少读的场景
乐观锁更轻量级,性能更高,应用于线程竞争比较少的情况,多度少写的场景
五、原子类
AtomicInteger类
原子整数,底层使用了CAS算法实现整数递增和递减操作
常用方法:
-
incrementAndGet 原子递增
-
decrementAndGet 原子递减
CAS算法存在的问题
1、ABA问题(线程将变量A修改成B,但在此前可能其他线程将A修改成B,然后又修改成A,CAS发现不了这种改变)
2、如果预期值于实际值不一致,会处于循环等待状态,对CPU消耗比较大
六、ThreadLocal
线程局部变量,会为每个线程单独变量副本,线程中的变量不会相互影响
以空间换时间
static int count = 0;
//integer初始化值为null,用匿名内部类重写方法修改初始值
static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
count ++;
//获得local的值后+1
local.set(local.get() + 1);
//输出为1
System.out.println(Thread.currentThread().getName() + "--->" + local.get());
}).start();
}
Thread.sleep(1000);
System.out.println(count);
//输出为0,每个线程都有一个local
System.out.println(local.get());
}
ThreadLocal底层的实现
数据结构:Map键值对的结构
通过当前线程,得到当前线程中的ThreadLocalMap集合
将数据绑定到该Map中
public void set(T value) {