java并发 - 多线程

11 阅读6分钟

前言

15

创建线程有几种方式?

大体上可以分为四种:

  1. 继承Thread类并重写run方法创建线程,这种方式实现简单但线程类不可以再继承其他类;

  2. 实现Runnable接口并重写run方法,这种方式避免了单继承局限性,编程更加灵活,实现解耦;

  3. 实现Callable接口并重写call方法,这种方式可以获取线程执行结果的返回值,并且可以抛出异常;

  4. 使用线程池创建。

并发量小用前三种方法否则就用线程池。

Runnable和Callable的区别?

这两个接口都是线程任务类的接口,区别点在于:

  1. Runnable接口run方法无返回值;Callable接口call方法有返回值,可获取线程类的执行结果;

  2. Runnable接口run方法只能抛出运行时异常,且无法捕获处理;Callable接口call方法允许抛出异常,可以获取异常信息。

start和run的区别?

  • run(): 封装了要被线程执行的代码,本质上就是一个普通方法,可以被调用多次;

  • start(): 用来启动线程,底层会自动去执行run方法中的代码,start方法只能被调用一次。

也就是启动线程的时候,只能调用start方法,如果调用的run方法,不会启动新线程,而是当普通方法调用执行。

notify和notifyAll的区别?

这两个方法都是用户唤醒被wait方法休眠的线程的,区别点在于:

  • notifyAll:唤醒所有wait的线程;

  • notify:随机唤醒一个wait线程。

sleep和wait的区别?

sleepwait都是Java中用来让线程暂时放弃CPU使用权,进入阻塞状态的方法。他们的主要区别点有下面几个:

  1. 方法归属不同sleepThread的静态方法,而waitObject的成员方法;

  2. 醒来时机不同sleep会在指定的时间后自动苏醒,而wait需要其他线程的唤醒;

  3. 锁特性不同sleep会自动释放锁,而wait不会自动释放锁;

  4. 使用限制不同wait必须用在synchronized代码块中,而sleep无此限制。

说一下线程的状态及转换?

线程共分为7种状态,分别是:新建、就绪、运行、终止以及阻塞、等待、计时等待,它们之间的转换关系是这样的:

  1. 当线程new出来之后,没有start之前就会处于新建状态;

  2. 当线程执行start方法之后,就进入就绪状态;

  3. 当就绪的线程一旦获取到了cpu的执行权,就可以进入运行状态;

  4. 当线程执行完了run方法之后,就进入了死亡状态。

这是一条正常的流程,但是代码在运行状态下可以因为一些原因进入到其它状态,比如说:

  1. 当进行抢锁操作时,抢锁失败就会进入到阻塞状态;

  2. 当代码调用了wait方法时,就会进入等待状态;

  3. 当代码调用了sleep方法时,就会进入计时等待状态。

现在有T1、T2、T3三个线程,如何保证它们按顺序执行?

有很多种方法,最简单的方式就是使用线程类的join方法实现。

join方法是Thread类中的一个方法,当一个线程执行到另一个线程的join()方法时,当前线程会暂停执行,直到被join的子线程执行完毕。具体来说就是:可以在t2之前调用t1.join(),在t3之前调用t2.join()

synchronized的实现原理是怎样的?

基于Monitor机制实现,每一个对象都有一个与之关联的监视器 (Monitor):

  1. 当一个线程想要访问某个对象的synchronized代码块,首先需要获取该对象的Monitor

  2. 如果该Monitor已经被其他线程持有,则当前线程将会被阻塞,直至Monitor变为可用状态;

  3. 当线程执行完成synchronized块的代码后,就会释放Monitor,并把Monitor返还给对象池

程序、进程和线程的关系是什么?

  • 进程:一个程序运行起来就是一个进程;

  • 线程:一个进程中可以执行多个任务,每个任务就是线程。

join、yield方法有什么区别?

  • join方法Thread类中的方法,当前线程挂起,等待被join的线程执行完再执行;

  • yield方法:也是Thread类中的方法,线程等待,相同或优先级别更高的线程线程来执行。相当于让出线程的占有权,但让出的时间不能设定。

什么是代码重排?

指编译器和处理器为了优化程序性能而对指令序列进行重新排序的手段。

用户线程和守护线程有什么区别?

  1. 用户线程:就是普通线程;

  2. 守护线程:在start方法前执行一个thread方法就变为守护线程,守护线程最典型的应用就是GC (垃圾回收器),如果JVM中仅仅剩下了守护线程,那么JVM会自动退出。

同步方法和同步块,哪个是更好的选择?

同步块是更好的选择,因为它不会锁住整个对象(也可以让它锁住整个对象)。同步方法会锁住整个对象,可能会导致他们停止执行并需要等待获得这个对象上的锁。

  • 同步方法是指在方法上添加synchronized关键字,可以保证同一时间只有一个线程访问该对象的同步方法。同步方法的好处是使用简单,代码量少,但是锁的范围较大,当多个线程需要同时访问一个对象的不同方法时,会导致性能下降;

  • 同步块是指使用synchronized关键字来锁定一个代码块,可以灵活控制锁的范围。同步块的好处是锁的范围更小,可以提高并发性能,但使用复杂,需要手动控制锁的范围。

Lock和synchronized的区别?

  • synchronized:关键字,修饰方法和代码块,有异常会自动释放锁;

  • Lock:接口,只能修饰代码块,需要在finally中释放锁,性能高,不会锁住整个对象。

volatile的关键作用是什么?

volatile关键字的作用是在设置共享变量时加在变量前,如:public volatile static 变量。它有如下作用:

  1. 变量可见:当一个共享变量被修改后,会立即将修改的值更新到主存,其他线程读取该变量时获取到的就是修改后的值。

  2. 防止指令重排