深入浅出并发编程
这是深入浅出并发编程,详细讲解并发编程的方方面面,在以后Android面试中,遇到并发问题,不再担忧,同时这些知识,也适用于Java开发
第一章:线程基础
1.1什么是进程,线程,并发和并行,同步,异步?
进程
:进程可以简单理解为一个应用,如QQ,微信,抖音,一般来说,一个应用就是一个进程,进程是资源分配的最小单位
线程
:一个进程可以有多个线程,线程是最小调度单位,线程也可以理解为一串代码指令
并发
:线程轮流使用cpu叫做并发
并行
:在多核情况下,两个任务可能同时执行,叫做并行
1.2创建线程有几种方式?
两种方式:
- 继承Thread
- 使用Thread + Runnable
使用Thread是将线程和任务放在了一起,使用Runnable则将线程和任务分开,因为使用接口,比继承更加灵活
1 | // 创建线程对象 |
1 | Runnable runnable = new Runnable() { |
另一种方式:FutureTask
1 | //方式三:使用FutureTask 可以返回结果 |
1.3线程常用的api方法?
方法名 | 说明 | 注意 |
---|---|---|
start() | 启动一个新线程 | 只能调用一次,调用多次,会抛出IllegalThreadStateException |
run() | 线程启动后会调用的方法 | |
join() | 插入线程 | 其他线程会等待该线程执行结束 |
join(long n) | 插入线程,等待n | 其他线程只等待n |
getState() | 获取线程状态 | Java 中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING,TIMED_WAITING, TERMINATED |
isInterrupted() | 判断线程是否被打断 | 不会清除打断标记 |
interrupt() | 打断线程 | 如果当前被打断的线程正处于阻塞状态,会抛出InterruptedException,并且清除打断标记(线程继续执行try catch后面的语句),如果是正在运行的线程,则会设置打断标记(但是不会打断线程的运行,只是做个标记) |
interrupted() | 判断线程是否被打断 | 同isInterrupted(),但是会清除打断标记 |
sleep(long n) | 让当前线程休眠,让出时间片 | |
yield | 礼让线程,主动让出时间片 | 让出时间片给其他线程,然后自身进入可运行状态,后面再竞争时间片 |
面试题:start和run的区别?
回答:start方法会开启一个线程去执行run方法里面代码,run方法不会,仍然是在主线程去直接执行run方法而已
面试题:sleep和yield的区别?
回答:调用sleep后进入time waiting状态,时间到了后,重新竞争时间片,yield则是主动让出时间片,进入runnable状态,去竞争下一个时间片
线程优先级:这个就不讲了,不太重要的知识点,在cpu不忙的情况下有点用,cpu很忙的话,优先级没啥用处,调度器可能会忽略它
1.5守护线程
什么是守护线程呢?
守护线程:需要等其他非守护线程执行结束后,自身立刻结束,不管自身代码是否执行完毕
1 | public class 守护线程 { |
如上代码中,当我们设置线程为守护线程,该线程有一些工作需要做,但是当主线程结束后,守护线程也会一起结束,不会执行任务了
1.6线程状态
从操作系统层面,有5种状态
- 初始状态
- 可运行状态
- 运行状态
- 阻塞状态
- 终止状态
从Java层面,有6种状态
- new 新建状态
- Runnable(包括可运行,运行)
- Block
- Time Waiting
- Waiting
- terminated
第二章 线程安全
2.1Java内存模型
主要理解的是工作内存和主内存,工作内存是线程私有的,可以简单理解为栈,而共享内存呢,可以简单理解为堆。
主内存的共享变量,需要读取到工作内存的变量副本,然后送给CPU寄存器处理数据,正是由于多个线程同时处理同一个共享变量数据,而导致的线程安全问题
2.2并发编程三要素
也可以说是线程安全安全的三个问题
- 原子性
- 可见性
- 有序性
原子性
指一个操作或多个操作要么全部执行,且执行的过程不会被任何因素打断,要么就都不执行,如:i++就不是原子性
实际案例:两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?
1 | static int counter = 0; |
结果有正数,负数,零,接下来分析一下i++的JVM字节码指令:
1 | getstatic i // 获取静态变量i的值 |
i–的JVM字节码指令:
1 | getstatic i // 获取静态变量i的值 |
在上面的代码中,问题就出在,因为不是原子性操作,当线程A的工作副本数据处理后,还没来得及写回主内存,时间片就用完,上下文切换,另一个线程B去主内存中读取共享变量(还是以前的值),处理完成后,写回主内存,然后上下文切换,线程A并不能感知到线程B修改了数据,即使说加了volatile,保证了可见性,但是没有保证原子性,这个时候,寄存器里处理好的中间变量,写回工作内存副本,再写回主内存共享变量,这个时候,就出错了!
解决原子性的办法:
- 阻塞式解决方案:synchronized加锁 Lock
- 非阻塞式的解决方案:原子变量
有一个概念:临界区
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
我们加锁,就是对这个临界区进行加锁
1 | /** |