参考资料:https://blog.csdn.net/cheidou123/article/details/90712178

1. 进程和线程

  • 进程:进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。在Java中,当启动main函数时其实就是启动了一个JVM的进程,而main函数所在的线程就是这个进程中的一个线程,也称主线程。
  • 线程:线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的方法区资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
  • 区别:进程是系统进行资源分配的一个独立单位,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,一个进程可以包括多个线程。
  • 线程的五个状态:
    • 新建:创建线程还没有start
    • 就绪:start但是还没有获取CPU时间片
    • 运行:正在执行
    • 阻塞:比如sleep等等状态
    • 死亡:正常退出或者使其发生异常

2. 线程的启动

  • 继承Thread
  • 实现Runnable接口,规定run()方法,该方法不能抛出异常。
  • 实现Callable接口,规定call()方法,Callable的任务执行后可返回值,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取将来结果;当不调用此方法时,主线程不会阻塞。该方法可以抛出异常。

3. 停止线程

  • stop()方法强行终止线程(不推荐的方法);
  • 使用退出标志(推荐);
  • 使用interrupt()方法中断线程(该方法并不能真正中断线程,需要其他方式配合)。

4. Sleep

  • 必须捕获异常;
  • 线程睡眠到期自动苏醒,并返回到可运行状态(就绪),而非运行状态。
  • sleep()没有释放锁。

5. Wait/Notify/NotifyAll

三者都需要在同步方法或者同步代码块调用,否则抛出java.lang.IllegalMonitorStateException运行异常。

  • 进入wait()后,当前线程释放锁;
  • notify()唤醒一个正在等待该对象的线程,然后竞争获取锁;
  • notifyAll()唤醒所有正在等待该对象的线程,然后竞争获取锁。

6. Yield

yield()使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。CPU会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到。

7. Join

join()主要作用是同步,使得线程之间的并行执行改变为串行。参数可以传入等待时间,若缺省或为0则等待执行完毕。

8. 线程优先级

优先级高的线程获得的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。

  • 线程的优先级用thread.setPriority(int a)来设置,a取值范围为0-10,默认为5。
  • 线程优先级有继承性,如果主线程启动threadA线程且threadA线程没有另外赋予优先级,则threadA线程优先级和main线程一样(即通过A线程启动线程B,线程B没有设置优先级则优先级同A一致)。
  • CPU尽量将执行资源让给线程优先级高的,即线程优先级高的总是会大部分先执行,但是不代表高优先级的线程全部都先执行完再执行低优先级的线。
  • 优先级在调用start()之前进行设置。

9. 内存可见性

(1)JVM
一个线程对共享变量值的修改能够及时被其它线程看到,则把这个共享变量叫做可见的。

  • 所有变量都存储在主内存中;
  • 每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本。

Java内存模型的规定:

  • 线程对共享变量的所有操作必须在自己的工作内存中进行,不能直接从主内存中读写。
  • 线程间变量值传递必须通过主内存。

如果线程1对共享变量的操作希望线程2看到,需要如下两个步骤:

  • 将共享变量刷新到主内存,
  • 更新到线程2工作内存。

(2)Synchronized

  • sychronized关键字可以实现原子性和可见性;
  • 加锁时清空工作内存共享变量的值,使用共享变量从主内存读取最新的值。

(3)Volatile

  • volatile关键字只能保证可见性,不能保证原子性。

10. 死锁

(1)产生死锁必须具备以下四个条件

  • 互斥条件:该资源任意一个时刻只由一个线程占用;
  • 请求与保持条件:一个线程因请求资源而阻塞时,对已经持有的资源保持不放;
  • 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕才释放资源;
  • 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

(2)如何避免死锁的产生
只需要破坏四个条件之一即可:

  • 破坏互斥条件:无法达到,因为加锁的目的就是形成互斥。
  • 破坏请求与保持条件:一次性申请所有的资源。
  • 破坏不剥夺条件:占用资源的线程进一步申请其他资源时,若申请不到,则可以主动释放自己占用的资源。
  • 破坏循环等待条件:靠按序申请资源预防。按某一顺序申请资源,释放资源时反序释放。

11. 守护线程

Java中的两种线程分别是用户线程守护线程,守护线程是最低优先级(如垃圾回收线程)。

12. sychronized关键字

(1)原理

  • 代码块同步:使用monitorentermonitorexit指令实现,monitorenter是在编译后插入到同步代码块的开始位置,monitorexit插入到同步代码块的结束处和异常处。任何对象都有一个monitor与之关联,当一个monitor被持有后,它将处于锁定状态。根据虚拟机规范的要求,在执行monitorenter指令时,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1;相应地,在执行monitorexit指令时会将锁计数器减1,当计数器被减到0时,锁就释放了。如果获取对象锁失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。
  • 方法同步:相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的,当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。本质上和代码块同步没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

(2)注意事项

  • sychronized获取的都是对象锁;
  • A线程持有object对象的Lock锁,B线程可以调用object对象的非synchronized方法,但不可以调用object任何的synchronized方法;
  • sychronized是可重入锁;
  • 若出现异常,锁将会自动释放;
  • static方法加synchronized是属于类的,非static方法是属于对象的。

13. ReentrantLock

(1)概述
sychronized局限性:

  • 当线程尝试获取锁时,如果获取不到会一直阻塞;
  • 如果获取锁的线程进入休眠或阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等到。

(2)锁的基本概念

  • 可重入锁:如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该方法中调用另外一个同步方法也同样持有该锁。也可以这样说:同一个线程可以多次获取同一把锁。ReentrantLocksynchronized都是可重入锁。
  • 可中断锁:如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
  • 公平锁:先到先得。

(3)ReentrantLock与sychronized关键字对比

  • 都是可重入锁。
  • sychronized是关键字,ReentrantLock是类。
  • sychronized通过JVM字节码实现,ReentrantLock通过CAS实现。
  • synchronized的加锁和释放锁是自动的,ReetrantLock需要手动加锁和释放锁。
  • synchronized是不可中断的,ReetrantLock可中断的。
  • ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。
  • ReetrantLocktryLock可以设置超时机制。

14. volatile关键字

(1)目的
保证变量的内存可见性,防止局部重排列。

(2)原理
volatile关键字增加内存屏障(内存栅栏),可以提供以下功能:

  • 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。
  • 它会强制将对缓存的修改操作立即写入主存。
  • 如果是写操作,它会导致其他CPU中对应的缓存行无效。

(3)与sychronized的区别

  • volatile只能修饰变量,性能好。synchronized可以修饰代码块,方法。
  • volatile不会出现阻塞,synchronized会出现阻塞。
  • volatile保持数据可见性,不支持原子性,synchronized保证原子性,也间接保证可见性。
  • volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程访问资源的同步性。

15. 线程池

(1)目的

  • 避免创建和销毁线程的系统开销;
  • 控制最大并发数,防止阻塞;
  • 对线程进行简单管理,如延迟执行。

(2)使用原理

  • 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务;
  • 线程数量达到了corePoolSize,则将任务移入队列等待;
  • 队列已满,新建线程(非核心线程)执行任务;
  • 队列已满,且总线程数已达到maximumPoolSize,则会采取拒绝策略。

(3)常见线程池

  • 可缓存线程池CachedThreadPool
    • 线程数无限制;
    • 有空闲线程则复用空闲线程,无空闲线程则新建线程;
    • 闲置线程60s会自动销毁。
  • 定长线程池FixedThreadPool:核心线程数=最大线程数,并且池内线程不会因为闲置超时而销毁,队列无限长,直到outOfMemory。
  • 定时线程池ScheduledThreadPool
    • 设置核心线程数,最大线程数为Integer.MAX_VALUE,其实也是无限长;
    • 支持延迟及周期性任务执行。
  • 单线程化线程池SingleThreadPool:有且仅有一个工作线程执行任务,所有任务按序执行,遵循队列规则。