JUI高并发-基础(1)

172 阅读6分钟
  • 1.进程,线程,协程有什么区别

    • 进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
    • 每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
    • 其实也就是同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
    • 线程的缺点在于:线程之间通过内存来共享数据,这也导致了一个问题,任何一个线程出错,进程中的所有线程都会跟着一起崩溃(因为内存被破坏了)
    • 为什么有了进程还要有线程呢:

      • 1.进程间的信息难以共享数据,父子进程并未共享内存,需要通过进程间通信(IPC),在进程间进行信息交换,性能开销较大。
      • 2.创建进程(一般是调用 fork 方法)的性能开销较大。
    • 协程(Coroutine)是用户态的线程;避免系统内核级的线程频繁切换,用户可以自行控制协程的创建于销毁;占用空间比线程更小,能够实现更好的高并发
  • 2.创建线程的有哪三种方法

    • 继承Thread类,编程简单,但是由于已经继承了Thread类就不能继承其它类了,如果想要多个线程多new Thread。需要实现run()方法
    • 实现Runnable接口,实例化对象,然后把这些对象作为参数放到Thread,如果想要多个线程多newThread而不是Runnable的实现类,需要实现run()方法
    • 实现Callable接口,继承的是泛型,实现的方法是call(),有返回值,返回值是泛型的类型,并且这个方法可以抛出异常。执行方法是,FutureTask futureTask = new FutureTask<>(callable);把接口实现类作为参数加入,然后再加入Thread

    • 注意:我认为大多数的线程都不是先写好类,而是直接在main中实例化,比如new Thread(()->{ 内容},"线程1");,这么做的好处是,我可以调用其它线程的方法,因为都在一个相同的作用域中;而且也容易获得同一把锁
  • 3.join是什么

    • 作用是将主程序进行阻塞等待,启动其它线程,直到下一个线程完成才回到住主线程执行。在main中,thread1.join()
    • 另一种写法就是:while (thread1.isAlive() || thread2.isAlive()) {}
    • join()本质其实就是用wait写的,既然用的是wait但是为什么不使用notifyAll()唤醒主线程呢

      • Thread类线程执行完run()方法后,一定会自动执行notifyAll()方法
  • 4.什么是死锁,如何避免

    • 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,但是资源又被另一个线程占有
    • 必须具备的条件

      • 1.互斥条件:一个资源只能由一个线程占有,临界资源需要互斥访问
      • 2.请求和保持条件:在因申请资源而阻塞时,不能对已有资源释放。也就是线程在阻塞时可以请求其它资源,并且保持已经拥有的资源不释放
      • 3.不剥夺条件:一个线程不能直接剥夺另一个线程的资源,只有等它使用完后才可以
      • 4.循环等待条件:若干个线程之间形成头尾相接的循环等待资源的关系
    • 避免的方法

      • 破坏后面三个条件即可条件

        • 第二个:如果申请不到就释放自己已经占有的资源
        • 第三个:一次性申请所有需要的资源
        • 第四个:必须按照某种顺序来申请资源
      • 锁排序法:

        • 某个线程只有获得A锁和B锁,才能对某资源进行操作,在多线程条件下,如何避免死锁? 通过指定锁的获取顺序,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可以避免死锁
  • 5.wait和sleep的区别,notify又有什么作用呢

    • sleep是线程主动阻塞指定时间,但是过了一段时间后就会恢复准备状态,注意不是运行状态,还需要等待CPU轮到它;并且睡眠不释放锁
    • wait需要和synchronized关键字一起使用,线程进入阻塞状态,当notify或者notifyall被调用后,会解除阻塞;具体来说,使用wait将释放锁和CPU资源,进入阻塞状态
    • 主动wait的线程,被唤醒后,状态一定会由WAITING变为RUNNABLE吗

      • 那么首先要明白notify究竟是什么意思,它不是立即释放锁,只有在结束运行完synchronized 的代码块,才会释放锁,它的作用是唤醒其它线程,那么该唤醒哪个呢,其它进程也是有优先级的,这就跟notifyAll()有所区别

        • 每个对象都有一个监视锁,Monitor维护着EntrySet和WaitSet,线程wait后先进入WaitSet,此时状态是Waitting,然后唤醒后进入entrySet,状态是blocked,这时候线程不是立即获得CPU资源而是在竞争锁,也就是:线程“被唤醒”和“获得锁”是两个过程。
    • 了解有限等待吗,如果在有限等待前有其他线程抢占了资源,线程还能继续往下走吗

      • 有限等待是指给定一个值,线程在这个时间段后主动唤醒自己
      • 不可以,因此此时仅仅是进入了entryset表示可以参与锁的竞争了,并没有获得CPU资源
  • 6.为什么不直接调用run()方法,而是start方法

    • new 一个 Thread,线程进入了新建状态;start()会执行响应的准备工作,线程进入就绪状态,但是此时并没有获得运行,要等待时间片轮转,而不是runnable;等抢占到CPU资源后,自动执行run()方法
    • 如果直接执行run方法,并不是在其它线程执行,而是在main主线程上执行
  • 7.yield方法是什么

    • 先检测当前是否有相同优先级的线程处于同就绪状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为“退让”
    • 只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可能在进入到暂停状态后马上又被执行。
    • 并没有进入阻塞或等待状态,而是进入就绪状态