并发编程(线程基础)

  • 线程和进程的区别
  • 并发与并行的区别
  • 线程创建方式
  • runnable和callable的区别
  • run()和start()的区别
  • 线程包括哪些状态,状态之间如何变化
  • 新建三个线程,如何按顺序执行
  • notify()和notifyAll()的区别
  • wait和sleep方法的区别
  • 如何停止一个正在运行的线程

一、线程和进程的区别

根本区别:进程是操作系统资源分配的基本单位,线程是CPU任务调度和执行的基本单位

对比

  • 进程是正在运行程序的实例,一个进程可以包含多个线程,不同线程执行不同任务
  • 各个进程拥有独立的内存资源,同一个进程下的所有线程共享当前进程内存资源
  • 对比进程,线程没有操作系统为其分配的独立内存空间,而是共享进程的内存空间,更加轻量,上下文切换成本更低。

二、并行和并发的区别 

在多核CPU下

并行:同一时间做多件事的能力,4核CPU同时执行4个线程

并发:同一时间只处理一个线程,各线程上下文快速切换

并发是逻辑上的同时发生(切换速度很快,感受不到)

并行是物理上的同时发生


三、线程创建的方式 

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口
  • 通过线程池创建

3.1、继承Thread类

  1. 创建Thread子类
  2. 子类重写run方法
  3. 创建子类对象
  4. 子类对象调用start()开启线程

并发编程(线程基础)_第1张图片

3.2、实现Runnable接口

  1. 创建Runnable实现类
  2. 重写run方法
  3. 实例化Runnable实现类对象
  4. 以该对象作为构造器参数构造Thread类
  5. 调用Thread对象的start()方法开启线程

并发编程(线程基础)_第2张图片

3.3、实现Callable接口

  1. 创建Callable实现类
  2. 重写call方法
  3. 实例化Callable实现类对象
  4. 将Callable实现类对象作为参数构建FutureTask对象
  5. 将FutureTask对象作为构造器参数创建Thread对象
  6. 调用Thread对象的start()方法开启线程
  7. 通过FutureTask对象的get()方法获取call方法返回值

并发编程(线程基础)_第3张图片

3.4、通过线程池创建 

并发编程(线程基础)_第4张图片


四、 runnable和callable的区别

对比一下

并发编程(线程基础)_第5张图片

1、方法名不同,Runnable为run方法,Callable为call方法

2、返回值不同,Runnable无返回值,Callable返回泛型,可配合FutureTask拿到返回结果

3、异常处理不同,Runnable异常需要内部处理,不能抛出。Callable可以抛出


 五、run()和start()的区别

并发编程(线程基础)_第6张图片

“start()方法会使得该线程开始执行;java虚拟机会去调用该线程的run()方法。”

因此,t.start()会导致run()方法被调用,run()方法中的内容称为线程体,它就是这个线程需要执行的工作。

用start()来启动线程,实现了真正意义上的启动线程,此时会出现异步执行的效果,即在线程的创建和启动中所述的随机性。‘ 

并发编程(线程基础)_第7张图片

而如果使用run()来启动线程,只是单纯的执行重写后的run方法,没有开启新线程。

并发编程(线程基础)_第8张图片

  • start():使新线程开始执行,jvm 自动调用该线程run()方法。异步操作
  • run():单纯的执行重写后的run方法,没有开启新线程。同步操作

 六、线程包括哪些状态,状态之间如何变化

Thread类中的枚举类State里有介绍了线程的六种状态

新建状态、可执行状态(阻塞状态、等待状态、计时等待状态)、死亡状态

并发编程(线程基础)_第9张图片

NEW ==> RUNNABLE ==> BLOCKED ==> WAITING ==>  TIMED_WAITING ==> TERMINATED

并发编程(线程基础)_第10张图片

简单来讲可以看出三个状态:新建状态、可执行状态、死亡状态

new Thread是新建状态(NEW),调用start()方法后进入可执行状态(RUNNABLE),拿到CUP执行全后进入死亡状态(TERMINATED),如果没有拿到CPU执行权,可能进入如下三种状态

  • 当前线程无法拿到锁,进入阻塞状态(BLOCKED),获得锁之后恢复可执行状态
  • 当前线程调用了wait()方法,进入等待状态(WAITING),其它线程调用nitify()唤醒后可恢复执行状态
  • 当前线程调用了sleep()方法,进入计时等待状态(TIMED_WAITING),到时间后恢复可为执行

 七、新建三个线程,如何按顺序执行

使用join方法

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("t1");
        });

        Thread t2 = new Thread(() -> {
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2");
        });

        Thread t3 = new Thread(() -> {
            try {
                t2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t3");
        });

        t2.start();
        t3.start();
        t1.start();

join方法加了synchronized,保证串行执行。

- 如果参数为负数,则直接报错:"timeout value is negative"

- 如果join方法参数为0,则会调用isAlive()方法,检测线程是否存活,如果存活就调用wait方法,一直等待。
- 如果参数大于0,使用while判断线程是否存活,存活的话就一直判断当前线程执行的时间并且计算还需要等待的时间,如果等待时间小于等于0就跳出循环,否则就继续wait

并发编程(线程基础)_第11张图片


八、notify()和notifyAll()的区别 

并发编程(线程基础)_第12张图片

notify:随机唤醒一个线程

notifyall:唤醒所有线程


九、wait和sleep方法的区别

相同点:

sleep和wait都会挂机线程,暂时放弃CPU资源

不同点:

  • 方法归属不同:sleep是Thread类的静态方法,wait是Object里的成员方法,每个类都有
  • 醒来时机不同:sleep(long)和wait(long)都会等待相应毫秒后醒来,wait()如果没有被notify()或notifyall()唤醒,会一直睡下去
  • 锁特性不同:sleep如果在同步代码块中执行(synchronized),不会释放对象锁(我放弃CPU资源,但是你们也不能用)。使用wait需要先获取对象锁,方法执行后线程会放弃对象锁,进入wait set等待,直到被唤醒。(我放弃了CPU资源,你们可以去用)

十、如何停止一个正在运行的线程 

可以用interrupt()方法打断正在运行的线程

打断的线程会发生上下文切换,操作系统会保留线程资源,重新抢占到CPU资源后会从打断初继续运行。

  • sleep、wait、join都会使线程挂起,此时打断线程会清除中断状态,并抛出interruptedException异常.
  • 打断正常运行的线程,不会清除中断状态

 

 

你可能感兴趣的:(面试篇,java,jvm,开发语言)