1. 上下文切换
即使单核CPU也支持多线程运行代码, CPU会给每个线程分配CPU时间片, 时间片一般是几十毫秒, 然后CPU通过时间片分配算法不停地切换线程, 循环执行任务, 让我们感觉多个线程是同时执行的
CPU在切换任务前会保存上一个任务的状态, 以便下次切回这个任务的时候, 可以再加载到这个任务的状态, 任务从保存到再加载的过程就是一次上下文切换
1.1 多线程一定快吗
线程有创建和上下文切换的开销, 当线程执行的任务过于简单, 那么线程的创建和上下文开销超过了任务的执行时间, 那么多线程可能会更慢
资源限制导致多线程更慢, 例如: 网络IO, 磁盘IO等
1.2 如何减少上下文切换
- 无锁并发编程
- 例如将数据ID按照Hash取模分段, 不同的线程处理不同段的数据
- CAS算法
- Atomic包使用了CAS算法更新数据, 而不需要加锁
- 使用最少的线程和协程
- 使用线程池
- 任务很少的时候, 不需要使用很多线程处理, 线程多的话, 大量线程处于等待状态
- 协程: 单线程实现多任务调度, 单线程里维护多个任务间的切换
2. 死锁
//死锁代码演示
public class DeathLocker {
//被线程1锁住, 线程2需要获取的对象
private Object lock1 = new Object();
//被线程2锁住, 线程1需要获取的对象
private Object lock2 = new Object();
public static void main(String[] args) {
new DeathLocker().lock();
}
//死锁
public void lock() {
//线程1
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//锁住lock1
synchronized (lock1) {
System.out.println("thread1 lock...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//sleep一秒, 获取lock2
synchronized (lock2) {
System.out.println("thread1 get lock2...");
}
}
}
});
//线程2
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//锁住线程2
synchronized (lock2) {
System.out.println("thread2 lock...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//sleep一秒, 获取lock1
synchronized (lock1) {
System.out.println("thread2 get lock1...");
}
}
}
});
//启动线程
thread1.start();
thread2.start();
}
}
运行结果:
thread1 lock...
thread2 lock...
上面的代码是简单的死锁演示
如何避免死锁
- 避免一个线程同时获取多个锁
- 避免一个线程内同时占用多个资源, 尽量保证每个锁只占用一个资源
- 尝试使用定时锁, 使用lock.tryLock(timeout)代替使用内部锁机制
- 对于数据库锁, 加锁和解锁必须在同一个数据库连接中, 否则会出现解锁失败的情况