多线程原理
为什么需要多线程?
-
CPU/内存/IO的巨大性能差异
-
多核CPU的发展
-
一个线程表现的本质就是多出来一套完整的方法栈
-
优点:多个执行流,并发执行
-
缺点:
- 占用资源:每个线程有独立的方法栈
- 慢,切换上下文
-
能不能让上下文切换尽可能少?
- 协程 - 用户态线程
-
Thread
- Thread类的每一个实例代表一个JVM中的线程
只有Thread是线程,其他的都不是线程(注释中提到),Runnable和Callable是一个任务,一小段代码
Runnable / Callable
- Runnable代表一个任务
- 可以被任何一个线程执行
- Callable解决了Runnable的一些问题
- Runnable不能返回值
- Runnable不能抛出checked exception
Runnable jdk1.0
Callable jdk1.5
笔记
-
多线程缺点:
- 代码复杂,变得很难理解
- 线程的互相交互和访问共享变量会引发各种问题,包括死锁等
-
try-catch只会捕获当前线程的异常
-
异常的抛出是顺着方法栈往外抛的
-
线程的底部要么是main()方法,要么是Thread.run()方法
“在可计算性理论里,如果一系列操作数据的规则(如指令集、编程语言、细胞自动机)可以用来模拟单带图灵机,那么它是图灵完备的。
- 当两个线程运行的时候,方法栈的所有东西都是私有的,其他的都是公有的。
Volatile
- 线程模型:
每一个线程可以有一个主内存的变量的副本,cpu定期同步回内存。
volatile的保证
- 可见性,并非原子性
- 写入volatile变量会直接写入主内存
- 从volatile变量读取会直接读取主内存
- 非常弱的同步机制
- 禁止指令重排
- 编译器和处理器都可能堆指令进行重排,导致问题
- 有同步的时候无需volatile
- synchronized/Lock/AtomicInteger
JUC:java.util.concurrent 并发工具包
- 同步:synchronized
- 协同:wait() / notify() / notifyAll()
synchronized
-
synchronized
- Java语言级的支持,1.6之后性能及大提高
- 字节码层面的实现:monitorenter / monitorexit
- 锁住的是?
- 对象
- 如果是静态方法,那么锁住的是该类的class
- 如果是非静态方法,那么锁住的是this对象
- Java语言级的支持,1.6之后性能及大提高
-
缺点
-
死板。比如:不能查看是否有别人拿到了锁对象。
-
只有悲观锁、排他锁,没有乐观锁、共享锁
-
一定要和一个对象(monitor,监视器)协同
wait和sleep有什么区别?
- wait() 之后锁对象就会被释放
- 如果已经获取了锁对象,那么sleep() 之后不会释放锁对象。如果没获取锁对象,那么不关sleep() 什么事。。。
JUC包AtomicXXX
- AtomicInteger / AtomicBoolean / AtomicLong / Atomic Reference..
- 全都是Compare And Swap,提高性能
CAS是一种有名的无锁(lock-free)算法。也是一种现代 CPU 广泛支持的CPU指令级的操作,只有一步原子操作,所以非常快。而且CAS避免了请求操作系统来裁定锁的问题,不用麻烦操作系统,直接在CPU内部就搞定了。
CAS有三个操作参数:
内存位置V(它的值是我们想要去更新的)
预期原值A(上一次从内存中读取的值)
新值B(应该写入的新值)
CAS的操作过程:将内存位置V的值与A比较(compare),如果相等,则说明没有其它线程来修改过这个值,所以把内存V的的值更新成B(swap),如果不相等,说明V上的值被修改过了,不更新,而是返回当前V的值,再重新执行一次任务再继续这个过程。
所以,当多个线程尝试使用CAS同时更新同一个变量时,其中一个线程会成功更新变量的值,剩下的会失败。失败的线程可以重试或者什么也不做。
Lock / Condition
Condition.java中的注释:
{@code Condition} factors out the {@code Object} monitor
methods ({@link Object#wait() wait}, {@link Object#notify notify}
and {@link Object#notifyAll notifyAll}) into distinct objects to
give the effect of having multiple wait-sets per object, by
combining them with the use of arbitrary {@link Lock} implementations.
Where a {@code Lock} replaces the use of {@code synchronized} methods
and statements, a {@code Condition} replaces the use of the Object
monitor methods.
- Lock/Condition与synchronized/wait/notify机制
- 更加灵活
- 同一个锁可以有多个条件
- 读写分离
- tryLock:尝试获得锁(相比synchronized的不能查看别人是否已经获得了锁,更加的方便)
- 可以方便的实现更加灵活的优先级/公平性?
- 公平锁和非公平锁
CountDownLatch
- 倒数闭锁
- 用于协调一组线程的工作
- API:
- countDown()
- await()
public class Juc {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
// 每个工人开始干活
int second = new Random().nextInt(10);
try {
Thread.sleep(second * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + finalI + "活干完了!");
//干完活倒计-1
latch.countDown();
}).start();
}
latch.await();
System.out.println("所有的工人都干完活了!");
}
}
CyclicBarrier
- API:
- await()
public class Juc {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(10);
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
// 每个工人开始干活
int second = new Random().nextInt(10);
try {
Thread.sleep(second * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + finalI + "活干完了!");
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("等待其他人到了才能继续!");
}).start();
}
}
}
Semaphore
- 信号量:获取和释放
acquire()获取
release()释放
BlockingQueue / BlockingDeque
- API:
- 阻塞操作:put / take
Future与ExecutorService
- Future代表⼀个「未来才会发⽣的事情」
- Future本身是⽴即返回的
- get()会阻塞并返回执⾏结果,并抛出可能的异常
线程池详解
- 线程池的参数们
- corePoolSize 核⼼员⼯数量
- maximumPoolSize 最⼤招募的员⼯数量
- keepAliveTime/unit 员⼯闲下来多久之后炒掉他们
- workQueue 订单队列
- threadFactory 造⼈的⼯⼚
- handler 订单实在太多的处理策略