进程与线程
进程和线程都是一个时间段的描述,是CPU工作时间段的描述。是运行中的程序指令的一种描述。
进程:进程就是上下文切换之间的程序执行的部分。是运行中的程序的描述,也是对应于该段CPU执行时间的描述。与之相关的东西有寻址空间,寄存器组,堆栈空间等。不同的进程,这些都不同,从而能相互区别。
线程:线程是共享了进程的上下文环境,更为细小的CPU时间段。线程有自己的程序计数器、堆栈、局部变量,线程主要共享的是进程的地址空间。线程的上下文切换代价远小于进程的上下文切换。
线程的创建方式
- 继承 Thread 类,重写 run() 方法,通过 start() 方法启动。
- 实现 Runnable 接口,重写 run() 方法。实现类 和 Thread 的代理模式(实现类负责真实的业务操作,Thread 负责资源调度与线程创建)共同完成多线程业务。
- 实现 Callable 接口,重写 call() 方法。与 Runnable 类似,不同的是该方法有返回值,可以获得异步执行的结果。通过 FutureTask/Future 来创建有返回值的 Thread 线程。
- 通过线程池创建,通过 Executor 的工具类可以创建不同类型的线程池,也可以直接通过创建 ThreadPoolExecutor 指定自定义的参数来创建线程池。
线程池的好处:- 重用线程池中的线程,避免频繁创建和销毁线程造成的性能损耗。
- 更加有效的控制线程的最大并发数,防止线程过多抢占资源造成的系统阻塞。
- 对线程进行有效的监控和管理。
ThreadPoolExecutor 参数说明:
- corePoolSize(核心线程数):在创建线程后,默认情况下线程池并没有任何线程,而是等待任务到来才创建线程去执行任务,当线程池中的线程数目达到 corePoolSize 后,新来的任务将会被添加到缓存队列中,即 workQueue。
- maximumPoolSize(线程池中的最大线程数):当线程池中的线程数等于 corePoolSize 并 workQueue 已满,这时如果线程数小于 maximumPoolSize 定义的值,则会继续创建线程去执行任务,否则将会调用相应的拒绝策略来拒绝这个任务。另外超过 corePoolSize 的线程被称为 “idle Thread”,这部分线程会有一个最大空闲存活时间(keepAliveTime),如果超过这个空闲存活时间还没有任务被分配,则会将这部分线程进行回收。
- keepAliveTime(控制 “idle Thread” 的空闲存活时间)
- unit(参数keepAliveTime 的时间单位)
- workQueue(阻塞队列):当线程池中的线程数目 >= corePoolSize,则每来一个任务,会尝试将其添加到该队列中,添加可能成功也可能不成功,成功的话就会等待空闲线程去执行该任务,若添加失败(一般是队列已满),就会根据当前线程池状态决定如何处理该任务(若线程数 < maximumPoolSize 则创建线程;若线程数 >= maximumPoolSize 则会根据拒绝策略做具体处理)。
- threadFactory(线程工厂):用来为线程池创建线程,不指定线程工厂时,线程池内部会调用
Executors.defaultThreadFactory()创建默认的线程工厂。 - handler(拒绝执行策略):当线程池的缓存队列已满并且线程池中的线程数目达到 maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。
线程的生命周期和状态
Java 线程状态:New(初始状态)、Runable(运行状态)、Blocking(阻塞状态)、Waiting(等待状态)、Time_Waiting(超时等待状态)、Terminated(终止状态)。-Java多线程/361817-20200718162809439-1402476361.png)
sleep() 和 wait() 的区别
- sleep() 方法没有释放锁,wait() 释放了锁。
- sleep() 方法属于 Thread 类的静态方法,作用于当前线程;而 wait() 方法是 Object 类的实例方法,作用于对象本身。
- 执行 sleep() 方法后,可以通过超时或调用 interrupt() 方法唤醒休眠中的线程;执行 wait() 方法后,通过调用 notify() 或 notifyAll() 方法唤醒等待线程。
死锁
死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个线程使用;
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放;
- 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺;
- 循环等待条件:若干线程之间形成一种头尾相接的熏昏等待资源关系。
解决死锁问题可通过破坏上述四个条件中的任意一个或着多个。
CAS 原理
CAS(即 Compare and Swap) 中文意思为比较并交换,是一种非阻塞算法实现,也是一种乐观锁技术,它能在不使用锁的情况下实现多线程安全,所以 CAS 也是一种无锁算法。
CAS 具体包括三个参数:内存值 V、旧的预期值 A 和 更新值 B,当且仅当预期值 A 和 内存值 V 相同是,将内存值修改为 B 并返回 true,否则什么都不做,并返回 false。
CAS 主要解决在不用加锁的情况下实现多线程安全,虽然很高效的解决了原子操作问题,但是 CAS 仍然存在三大问题:
- 循环时间长,开销很大
当某一方法执行时,如果 CAS 失败,会一直进行尝试,如果 CAS 长时间尝试不成功,可能会给 CPU 带来很大的开销。 - 只能保证一个共享变量的原子操作。
- 存在 ABA 问题
ABA 问题是 CAS 中的一个漏洞。CAS 的定义,当且仅当内存值 V 等于就得预期值 A 时,CAS 才会通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。那么如果先将预期值 A 给成 B,再改回 A,那CAS 操作就会误认为 A 的值从来没有被改变过,这时其他线程的 CAS 操作仍然能够成功,但是很明显是个漏洞,因为预期值A的值变化过了。如何解决这个异常现象?java并发包为了解决这个漏洞,提供了一个带有标记的原子引用类AtomicStampedReference,它可以通过控制变量值的版本来保证 CAS 的正确性,即在变量前面添加版本号,每次变量更新的时候都把版本号 +1,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。