初学线程,线程的安全问题

一、线程的上下文切换

前提:一个CPU的内核一个时间只能运行一个线程中的一个指令

线程并发:CPU内核会在多个线程间来回切换运行,切换速度非常的快,达到同时运行的效果

线程切换回来后,如何从上次执行的指令后继续执行?

程序计数器(每个线程都有属于自己的独立计数器,用于记录上次执行的行数)

线程执行会随时切换,如何保证重要的指令能完全完成?

线程安全问题

CPU进行上下文切换的过程中性能会降低


二、线程的安全(同步)问题

CPU在多个线程间切换,可能会导致某些重要的指令不能完整执行,出现数据错误的问题

出现线程安全问题的三个条件:

  1. 多个线程
  2. 同一时间
  3. 执行同一段指令或修改同一变量

三、线程安全问题的解决办法

解决方法:给程序上锁,让当前线程完整执行一段指令,执行完后释放锁,其他线程再执行

几种上锁方式:

  1. 同步方法
  2. 同步代码块
  3. 同步锁

同步方法

给方法添加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) {

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值