多线程之---线程同步

时间:2022-09-19 18:28:57

线程同步

Java平台用于协调线程间共享数据访问的关键字:volatile,synchronized,final,static;机制:锁;API:Object.wait()/Object.ntify()

锁也称互斥锁/排他锁,是控制原本并行访问共享变量的线程改为串行访问,每次访问变量的线程需要持有锁,锁可以被看作一个许可证,持有者才可以通行,线程完成逻辑操作(这里获得锁后执行的代码成为临界区)后将释放锁,给下一个线程持有;

锁导致的问题

  • 开销:上下文切换

  • 锁泄漏:由于程序错误,锁一直被一个线程占用,反复使用,其他线程永远不能持有

  • 死锁

锁的作用

​ 能保护共享变量保证线程安全,即原子性/可见性/有序性,但需要保证所有访问同一临界区代码的线程都使用同一个锁,即使只是读线程也需要持有锁

  • 原子性

    锁通过互斥保证原子性,即一次只有一个线程可以执行,其他线程不能打扰;如果这个线程异常退出(非自主切换)了,那么线程刚刚做的事还存在吗,还能保证原子性吗

  • 可见性

    锁获得时会将写线程对共享变量做的更新同步到该线程执行处理器的高速缓存中(保证这个线程拿到的是较新结果);锁释放时,写线程对共享变量所作的更新能被推送到该线程执行处理器的高速缓存中,从而对读线程同步(读到较新结果);原子性加上可见性保证持有锁的线程读到的共享变量(引用型共享变量/数组变量各个元素)的结果是最新结果

  • 有序性

    因为原子性和可见性保障的结果,读线程对写线程操作的感知顺序和源代码一致;但是不能保证临界区内的内存操作不会被重排序,因为原子性,这个重排序不会对其他线程产生影响

可重入性

​ 一个线程在持有一个锁的时候,这个锁未被释放时还能再对这个锁成功申请持有(TODO 如何实现的)

内部锁和显式锁

  • 内部锁

    由synchronized关键字实现,也称监视器,属于非公平锁

    • synchronized关键字,它的锁句柄通常使用private final修饰,防止其变量值改变,导致多个线程使用不同的锁,产生竞态;

      Java虚拟机代为执行内部锁的申请与释放,并且对程序中抛出却未被捕获的异常做了特殊处理,所以不会出现锁没有被释放的情况,以规避锁泄漏的问题

      内部锁的调度与Java虚拟机有关,它会为每个内部锁分配一个入口集合,用于存储等待获取锁的相应线程,当锁被占用时,这个集合内的线程(申请锁失败)都处于暂停blocked状态,并等待再次申请锁的机会,当锁被释放时,这个集合内的线程将会被唤醒,与新创建的线程一起抢占这个被释放的锁

      jdk1.6/1.7做了一些优化,如锁消除,锁粗化,偏向锁,适配性锁,减少了与显式锁之间的可伸缩性差异TODO

  • 显式锁

    由j.u.c.l包下的Lock接口的实现类实现,既可以支持公平锁也可以支持非公平锁(默认)

    tryLock方法支持短时间内申请锁,如果没有申请到就直接返回false

  • 读写锁

    共享/排他锁,一个锁扮演两种锁的角色。读锁:对应的写锁没有被任何线程持有时,允许多个读线程同时读取共享变量;写锁:对应的写锁和读锁没有被任何其他线程持有,以独占的方式访问共享变量

    适用于只读操作比写操作频繁,读锁持有时间较长

内存屏障

内存屏障禁止编译器/处理器重排序从而保证有序性,但同时有两个动作一并执行:刷新处理器缓存和冲刷处理器缓存,这两个动作就是可见性保证的基础。

那如果没有可见性需求或者有序性需求时,该怎么办?