zoukankan      html  css  js  c++  java
  • 并发编程基础知识

    创建线程的三种方式

    1. 继承Thread对象,重写run方法
    2. 实现runnable接口,作为Thread构造参数 - Thread默认的run()方法中会调用runnable对象的run()方法
    3. 实现callable接口,配合FutureTask对象使用 - 底层依然是runnable接口,通过共享变量实现线程之间的控制和通信
    /*
     *  实质:
     *    - Thread对象在start()方法中调用native方法通知操作系统创建线程,
     *    - 线程创建后,会回调Thread对象的run()方法实现业务调用
     */
    public class ThreadTest {
        public static void main(String[] args) throws Exception{
            // 1. Thread类通过start()方法通知操作系统构建线程,操作系统创建线程后会回调Thread对象的run()方法
            Thread myThread = new MyThread();
            myThread.start();
    
            // 2. Thread类中存在一个runnable对象,默认的run()方法中会调用runnable对象的run()方法
            Runnable myRunnable = new MyRunnable();
            new Thread(myRunnable).start();
    
            // 3. 本质是对runnable接口的封装,添加future接口的实现,通过共享变量实现主线程与子线程之间的信息传递,包括:控制和返回值
            Callable callable = new MyCallable();
            FutureTask<String> task = new FutureTask<String>(callable);
            new Thread(task).start();
            
            String answer = task.get();
        }
    }
    

    操作系统中线程的生命周期

    新建(New):新创建出来的线程,尚未执行start()方法
    就绪(Ready):等待操作系统分配CPU时间片
    运行(Running):正在运行中的线程
    阻塞(Blocked):等待锁或者等待被唤醒
    终结(Terminated):执行完成的线程
    操作系统线程状态转换

    JMM中线程的生命周期

    新建(New):新创建出来的线程,尚未执行start()方法
    运行态(Running):操作系统中就绪态和运行态的集合,代表正在执行中的线程
    阻塞(Blocked):由Synchronized触发的阻塞状态
    等待(Waiting): 等待被唤醒
    限时等待(Timed Waiting):带超时时间的Waiting状态
    终结(Terminated):执行完成的线程
    java线程状态转换
    tips: LockSupport是JMM为实现锁机制提供的类,其底层调用unsafe的park/unpark()方法

    interrupt 线程中断标志

    JMM底层为Thread提供了interrupt标志位,用于控制线程的执行。

    1. interrupt():将标志位设置为true(此时,会唤醒处于part()的线程)
    2. interrupted():将标志位恢复为false(使标志位复位,以等待下一次中断信号)
    3. isInterrupted():获取线程的中断状态

    tips:interrupt标志位用于优雅地中止线程,当interrupt标志被置为true时,会唤醒对应的线程,由线程内部实现决定后续逻辑(InterruptedException)
    tips:interrupt只发送中断信号,而是否中断以及具体的中断逻辑由线程自行决定

    synchronized(类锁/对象锁)

    1. 特性:

      • 不可中断
      • 可重入(底层有重入计数器)
      • 非公平锁
    2. JDK 6对synchronized的优化:无锁化(偏向锁,轻量级锁)
      HotSpot class header

      • 偏向锁:适用场景 - 在同步周期内大概率不存在并发问题,通过CAS设置偏向标志,代表已有线程获取锁
      • 轻量级锁(自适应自旋锁):适用场景 - 大部分锁都会在很短时间内被释放,通过CAS自旋不断获取锁,避免线程挂起的损耗
      • 重量级锁:通过Monitor对象实现锁,内部实现为EntryList和WaitSet两个阻塞队列 - 用于同步状态(锁)和等待状态(wait - notify)

    Synchronized - Moniter监视器锁的实现

    死锁的条件

    • 互斥,共享资源 X 和 Y 只能被一个线程占用;
    • 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
    • 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
    • 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

    volatile

    并发编程问题:

    1. CPU存在多级缓存(会导致缓存一致性问题)
    2. 对指令实现重排序优化(编译器优化重排序/指令并行重排序/内存系统重排序),导致多线程任务下指令执行顺序的不可预测

    volatile:

    • 即时可见性:修饰变量发生修改,会立刻被其他线程感知(可见) - 缓存一致性协议
    • 避免指令重排序:在修饰变量处插入内存屏障,避免指令重排序(实现指令的happen-before关系) - Lock字节码指令

    缓存一致性协议(MESI):

    • M(Modify):缓存已被修改
    • E(Exclusive):独占缓存,数据只缓存在了当前CPU缓存中,未被修改
    • S(Shared):共享缓存,多个CPU均存储了该缓存,且未被修改
    • I(Invalid):缓存已失效

    内存屏障:

    • 读内存屏障(处理失效的缓存): volatile在读操作之前会插入一个load屏障(刷新无效缓存,得到最新的值)
    • 写内存屏障(将当前缓存的值写回主存): volatile变量在写操作之后会插入一个store屏障(将之前store的缓存写入主存)
    • 读写内存屏障

    Happens-Before模型

    • 程序次序规则(as-if-serial语义):单线程环境下,执行结果不变
    • volatile变量规则: volatile 修饰的变量的写操作,一定happens-before后续对于volatile变量的读操作.
    • 传递性规则:若a happens-before b,b happens-before c,则有 a happens-before c
    • start()规则:如果线程A执行操作ThreadB.start(),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作
    • Join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

    Thread.join()

    使当前线程阻塞,等待Thread结束后被唤醒,让线程的执行结果对后续线程可见(实现线程间的顺序性)
    join代码流程图

    ThreadLocal - 线程隔离存储机制

    // ThreadLocal.class#get()
        public T get() {
            Thread t = Thread.currentThread();
            // 获取当前线程的ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                // 从ThreadLocalMap中获取当前ThreadLocal对应的值
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    

    ThreadLocal底层结构

    tips

    1. sleep(), join() 和 yiled() 的区别
    • sleep: 让线程睡眠指定时间,会释放cpu时间片(单不会释放锁资源)
    • join - wait()/notify(): 让主线程等待join线程的执行结果(happens-before模型join规则)
    • yiled: 让出时间片,触发重新调度,效果等同于sleep(0)
    1. i++操作是线程安全的吗?
      不是,i++底层字节码分为load,store两条指令,非原子性,可能存在并发问题

    欢迎疑问、期待评论、感谢指点 -- kiqi,愿同您为友

    -- 星河有灿灿,愿与之辉

  • 相关阅读:
    Solo 博客系统 1.7.0 发布
    <Android 基础(二十九)> Fragment (2) ~ DialogFragment
    2016最新Java学习计划
    <Android 基础(二十八)> Fragment (1)
    2016最新前端学习计划
    Android 学习路线图
    C/C++学习路线图
    小学数学题,你会吗?
    劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(八)
    劣质代码评析——《写给大家看的C语言书(第2版)》》附录B之21点程序(七)
  • 原文地址:https://www.cnblogs.com/kiqi/p/14378975.html
Copyright © 2011-2022 走看看