Java 线程安全

Posted 朵朵瞎胡闹

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 线程安全相关的知识,希望对你有一定的参考价值。

引入1:计算机内存模型

1、因为向主内存中读写数据的速度要远远小于CPU处理数据的速度,所有引入了高速缓存的概念(高度缓存中读写数据的速度相接近CPU处理数据的速度)
2、将主内存中的数据读到高速缓存中,再由高速缓存读到寄存器中进行操作,操作成功后,再由寄存器刷新到高速缓存,最后再由高速缓存刷新到主内存(不是立即),这样以来对主内存中读写数据缓慢问题就不会影响到CPU的执行效率
3、当多个CPU的运算内容都涉到主内存中的相关数据时,将可能导致各自高速缓存中数据不一致的问题

引入2:JVM中定义了Java Memory Model的规范,该规范屏蔽了Java应用程序在各个操作系统中对主内存访问的差异性,但并不能保证在多线成环境下对主内存访问的一致性和指令重排问题

栈:随着线程的创建而创建,线程执行完毕栈也就销毁了
堆:新生代、老年代、永久代(1.8之前对方法区的实现)
方法区:1.8开始使用元空间实现,使用的是本地物理内存

引入3:Java内存模型和硬件内存模型的关联

1、线程之间的共享变量存储于主内存中
2、每个线程的栈(私有工作内存)优先存储于寄存器或高速缓存中,其次才是主内存中
3、JMM规定:1、所有变量(除局部变量)都存储在主内存,2、因为线程不能直接操作主内存中的共享变量,所以每个线程在创建的时候都会为其分配一个私有的工作内存(栈),只能将主内存中的共享变量读取到工作内存中才能进行操作,操作成功后再由工作内存同步到主内存中。

非锁机制

  • volatile关键字
    volatile关键字修饰的变量会强制线程从主内存中读取共享变量,操作过程中共享变量发生变化时会立即刷新到主内存,从而保证了共享变量在多线程环境的可见性,但并不能保证该共享变量复合操作的原子性
  • Atomic原子类
    Atomic原子类只能保证共享变量在多线程下的原子性,底层采用CAS(compare and set)非锁机制:读取当前变量的值为A,经过计算后变为B,再写入当前变量之前需要判断该变量的值是否改变,没有改变则更新,改变则获取改变后的值再走一遍之前的流程。因为比较的是变量值是否改变,所以会出现ABA的问题,即当前变量改变后的值和之前一样,可以通过为该变量加版本号的形式来区分

锁机制(JVM锁)

synchronized关键字和ReentrantLock类都是通过加锁来保证同一时刻只有一个线程执行同步代码,并且在锁释放之前将线程工作内存中的共享变量同步到主内存中
  • synchronized 关键字
@RestController
@RequestMapping("/wx/hkSeckill")
public class HkSeckillControler {
    @Resource
    private HkSeckillService hkSeckillService;

    // http://127.0.0.1:8080/warboot/wx/hkSeckill/seckill
    @RequestMapping("seckill")
    public JSONObject seckill() {
        return hkSeckillService.seckillBySynchronized();
    }
}
//
public interface HkSeckillService extends IService<HkSeckillEntity> {
    JSONObject seckillBySynchronized();
}
//
public class HkSeckillServiceImpl extends ServiceImpl<HkSeckillMapper, HkSeckillEntity> implements HkSeckillService {
    @Resource
    private HkService hkService;
    @Override
    public synchronized JSONObject seckillBySynchronized() {
        // TODO 查询库存信息
        QueryWrapper<HkEntity> hkEntityQueryWrapper = new QueryWrapper<HkEntity>();
        hkEntityQueryWrapper.eq("id", 1);
        HkEntity hkEntity = hkService.getOne(hkEntityQueryWrapper);
        int count = hkEntity.getCount();
        if (--count >= 0) {
            // TODO 新增抢单记录
            HkSeckillEntity HkSeckillEntity = new HkSeckillEntity();
            HkSeckillEntity.setOpenid(Thread.currentThread().getName());
            HkSeckillEntity.setStock(count);
            this.save(HkSeckillEntity);
            // TODO 库存减1
            UpdateWrapper<HkEntity> updateWrapper = new UpdateWrapper<HkEntity>();
            updateWrapper.set("count", count);
            updateWrapper.eq("id", hkEntity.getId());
            hkService.update(updateWrapper);
            //
            return ResponseUtil.success();
        } else {
            return ResponseUtil.fail("抢完了");
        }
    }
}

  • ReentrantLock 类
@RestController
@RequestMapping("/wx/hkSeckill")
public class HkSeckillControler {
    @Resource
    private HkSeckillService hkSeckillService;

    // http://127.0.0.1:8080/warboot/wx/hkSeckill/seckill
    @RequestMapping("seckill")
    public JSONObject seckill() {
        return hkSeckillService.seckillByReentrantLock();
    }
}
//
public interface HkSeckillService extends IService<HkSeckillEntity> {
    JSONObject seckillByReentrantLock();
}
//
@Service
public class HkSeckillServiceImpl extends ServiceImpl<HkSeckillMapper, HkSeckillEntity> implements HkSeckillService {
    @Resource
    private HkService hkService;

    // TODO 创建显示锁,锁的是当前对象
    Lock lock = new ReentrantLock();

    @Override
    public JSONObject seckillByReentrantLock() {
        boolean flag = true;
        try {
            flag = lock.tryLock(3, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (flag) {
            // TODO 查询库存信息
            QueryWrapper<HkEntity> hkEntityQueryWrapper = new QueryWrapper<HkEntity>();
            hkEntityQueryWrapper.eq("id", 1);
            HkEntity hkEntity = hkService.getOne(hkEntityQueryWrapper);
            int count = hkEntity.getCount();
            if (--count >= 0) {
                try {
                    // TODO 新增抢单记录
                    HkSeckillEntity HkSeckillEntity = new HkSeckillEntity();
                    HkSeckillEntity.setOpenid(Thread.currentThread().getName());
                    HkSeckillEntity.setStock(count);
                    this.save(HkSeckillEntity);
                    // TODO 库存减1
                    UpdateWrapper<HkEntity> updateWrapper = new UpdateWrapper<HkEntity>();
                    updateWrapper.set("count", count);
                    updateWrapper.eq("id", hkEntity.getId());
                    hkService.update(updateWrapper);
                } catch (Exception e) {
                    lock.unlock();
                    throw new BaseExceptionUtil(e.getMessage());
                }
                lock.unlock();
                return ResponseUtil.success();
            } else {
                lock.unlock();
                return ResponseUtil.fail("抢完了");
            }
        } else {
            return ResponseUtil.fail("获取锁超时");
        }
    }

总结:

synchronized 关键字
1、在保证线程安全的同时会导致其它线程的阻塞
2、线程执行成功后或执行过程中发生异常都会自动释放锁
3、修饰普通方法时锁的是当前实例对象;synchronized (this) {} 同步代码块
4、修饰静态方法时锁的是所有实例对象;synchronized (.class) {} 同步代码块
ReentrantLock1、线程取锁失败后会进入等待状态,超过指定时间后会直接返回false,而不会像synchronized一样阻塞其它线程
2、程序执行完毕或者出现异常时需要手动释放锁,否则会出现死锁
3、可中断锁
4、默认采用非公平锁,根据需求可以设置成公平锁,而synchronized只能是非公平锁
公平锁:线程取锁失败后会进入等待队列,先进入队列的线程会先获得锁
非公平锁:线程取锁失败后会进入等待队列,但等待线程取锁的概率是随机的

以上是关于Java 线程安全的主要内容,如果未能解决你的问题,请参考以下文章

怎样去写线程安全的代码(Java)

怎样去写线程安全的代码(Java)

Java线程安全问题代码实现

java是线程安全的吗

HashMap 和 ConcurrentHashMap 的区别

Java线程 — 线程同步及安全问题