>记录一下关于多线程的相关知识。
### 1. Java创建线程后,调用start()方法和run()的区别
1) start方法:
用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
2) run():
run()方法只是类的一个普通方法而已,如果直接调用run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待,run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:**调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。**
### 2. 常用的线程池模式以及不同线程池的使用场景
**五种线程池,四种拒绝策略,三种阻塞队列**
**三种阻塞队列**:
BlockingQueue<Runnable> workQueue = null;
workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界
workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界
workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界
**四种拒绝策略**:
RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
rejected = new ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
**五种线程池**:
ExecutorService threadPool = null;
threadPool = Executors.newCachedThreadPool();//有缓冲的线程池,线程数 JVM 控制
threadPool = Executors.newFixedThreadPool(3);//固定大小的线程池
threadPool = Executors.newScheduledThreadPool(2);
threadPool = Executors.newSingleThreadExecutor();//单线程的线程池,只有一个线程在工作
threadPool = new ThreadPoolExecutor();//默认线程池,可控制参数比较多
>参考我之前的文章:https://lixj.fun/archives/2020-04-30-11-39-14
CachedThreadPool
这类线程池的特点就是里面没有核心线程,全是非核心线程,其maximumPoolSize设置为Integer.MAX_VALUE,线程可以无限创建,当线程池中的线程都处于活动状态的时候,线程池会创建新的线程来处理新任务,否则会用空闲的线程来处理新任务,这类线程池的空闲线程都是有超时机制的,keepAliveTime在这里是有效的,时长为60秒,超过60秒的空闲线程就会被回收,当线程池都处于闲置状态时,线程池中的线程都会因为超时而被回收,所以几乎不会占用什么系统资源。任务队列采用的是SynchronousQueue,这个队列是无法插入任务的,一有任务立即执行,所以CachedThreadPool比较**适合任务量大但耗时少的任务**。
FixedThreadPool
这类线程池的特点就是里面全是核心线程,没有非核心线程,也没有超时机制,任务大小也是没有限制的,数量固定,即使是空闲状态,线程不会被回收,除非线程池被关闭,从构造方法也可以看出来,只有两个参数,一个是指定的核心线程数,一个是线程工厂,keepAliveTime无效。任务队列采用了无界的阻塞队列LinkedBlockingQueue,执行execute方法的时候,运行的线程没有达到corePoolSize就创建核心线程执行任务,否则就阻塞在任务队列中,有空闲线程的时候去取任务执行。由于该线程池线程数固定,且不被回收,线程与线程池的生命周期同步,所以**适用于任务量比较固定但耗时长的任务**。
ScheduledThreadPool
这类线程池核心线程数量是固定的,好像和FixThreadPool有点像,但是它的非核心线程是没有限制的,并且非核心线程一闲置就会被回收,keepAliveTime同样无效,因为核心线程是不会回收的,当运行的线程数没有达到corePoolSize的时候,就新建线程去DelayedWorkQueue中取ScheduledFutureTask然后才去执行任务,否则就把任务添加到DelayedWorkQueue,DelayedWorkQueue会将任务排序,按新建一个非核心线程顺序执行,执行完线程就回收,然后循环。任务队列采用的DelayedWorkQueue是个无界的队列,延时执行队列任务。综合来说,这类线程池**适用于执行定时任务和具体固定周期的重复任务**。
SingleThreadPool
这类线程池顾名思义就是一个只有一个核心线程的线程池,从构造方法来看,它可以单独执行,也可以与周期线程池结合用。其任务队列是LinkedBlockingQueue,这是个无界的阻塞队列,因为线程池里只有一个线程,就确保所有的任务都在同一个线程中顺序执行,这样就不需要处理线程同步的问题。这类线程池**适用于多个任务顺序执行的场景**。
### 3.什么是进程?什么是线程?有什么区别
1、进程是程序的一次执行过程,是系统运行程序的基本单位。进程是动态的,系统运行一个程序的过程即使一个程序从创建、运行到消亡的过程。
2、线程是比进程更小的执行单位,一个进程执行过程中可以产生多个线程。同类的多个线程共享进行的堆和方法区资源。每个线程都有自己程序计数器、虚拟机栈、本地方法栈。系统在产生一个线程、或者是各个线程之间在切换时,负担比进程要小,因此,线程也成为轻量化进程。
一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。
总结: **线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反**
### 4.线程的生命周期

1. 新建状态:
用new关键字建立一个线程后,该线程对象就处于新建状态。处于新生状态的线程有自己的内存空间,通过调用start()方法进入就绪状态。
2. 就绪状态:
处于就绪状态线程具备了运行条件,但还没分配到CPU,处于线程就绪队列,等待系统为其分配CPU。当系统选定一个等待执行的线程后,它就会从就绪状态进入运行状态,该动作称为“CPU调度”。
3. 运行状态
在运行状态的线程执行自己的run方法中代码,直到等待某资源而阻塞或完成任何而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。
4. 阻塞状态
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续执行。
5.死亡状态
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有三个,一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性地终止,如通过stop方法来终止一个线程【不推荐使用】;三是线程抛出未捕获的异常。
### 5.线程sleep()和wait()方法的区别和共同点
1、sleep()方法没有释放锁,wait()方法释放了锁
2、两者都可以暂停线程的执行
3、wait通常被用于线程的交互/通信,sleep被用于暂停执行
4、wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者notifyAll()方法。sleep()方法执行完后,线程会自动苏醒。使用wait()超时后线程会自动苏醒
### 6.为什么调用start()方法会执行run()方法,为什么我们不能直接调用run()方法
创建一个线程(new Thead),线程会进入新建状态;
调用start()方法后,会启动线程并进入就绪状态,当线程分配到时间片后就可以运行,进行运行状态。
start()会执行线程的相应准备工作,然后自动执行run()方法;如果只是单独执行run()方法,会把run()方法当作一个main线程下的普通方法来执行,并不会在某个线程中执行它,不算是多线程工作。
**调用start方法可以启动线程并使线程进入就绪状态,而run方法只是thread的一个普通方法调用,还是在主线程中执行**
### 7.synchronized关键字
**synchronized关键字主要是解决多个线程之间访问资源的同步性,synchronized关键字可以保证修饰的方法或代码块在任意时刻只能有一个线程执行**
synchronized 关键字最主要的三种使用方式
1. 修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
2. 修饰静态方法:给当前类加锁,会作用于类的所有对象实例
3. 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁
synchronized 关键字加到 static 静态方法和 synchronized 代码块上是给Class 类上锁。synchronized 关键字加到实例方法上是给对象加锁
### 8. Synchronized 和 ReenTrantLock 的对比
具体学习见以下链接,
https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md
https://lixj.fun/archives/%E8%B0%88%E8%B0%88synchronized%E4%B8%8Ereentrantlock%E7%9A%84%E5%8C%BA%E5%88%AB?token=a4e096986aa04e1ebcd7e0d2348959ef
### 9. volatile关键字
1、java内存模型
线程把主存的变量保存到本地内存,而不是直接在主存中进行读写。这种情况会导致一个线程在主存中修改了一个变量的值,而另外一个进程还继续使用自己本地内存拷贝的值,造成数据不一致。

要解决以上问题,需要将变量声明为volatile,每次使用的时候从主存进行获取

volatile的主要作用是保证变量的可见性,还有就是防止指令重排
### 10. synchronized 和 volatile 的区别
1、volatile关键字是线程同步的轻量化实现,所以volatile的性能会比synchronized好一些。
2、volatile只能用于变量,synchronized可以用于修饰方法和代码块。实际开发中使用synchronized的场景多一些
3、多线程访问volatile不会阻塞,synchronized可能会发生阻塞
4、volatile可以保证数据的可见性,不能保证数据的原子性。synchronized既可以保证可见性又可以保证原子性
5、volatile主要用于解决多个线程之间变量的可见性,synchronized主要用来解决多个线程之间访问资源的同步性
### 11.线程池相关
看下我以前的文章,
[Java中的线程池](https://lixj.fun/archives/2020-04-30-11-39-14)
[Runnable和Thread比较](https://lixj.fun/archives/2020-04-30-11-43-34)

笔记(四)- 多线程