并发编程
- 为什么要使用并发编程?
- 并发编程所带来的问题,以及如何避免?
并发编程是为了让程序运行的更快
使用并发编程的目的是加快程序的运行速度,但同样的使用并发编程会带来新的问题。上下文切换问题、死锁问题
上下文切换
问:上下文切换是什么,导致产生上下文切换问题的原因是什么呢?
答:对于单核处理器的计算机而言,CPU是通过为多线程分配CPU时间片来实现多线程的,多个线程共同抢占CPU分配的有限时间片,时间片抢占执行完后,返回就绪状态,或者程序执行完毕。但是在当前任务抢占完并执行完一个时间片后,切换前会保留上一个任务的状态,方便下一次切换回加载这个任务。所以,任务从保存到加载的过程就是一次上下文的切换。
可想而知,这个保留当前任务,并再次加载任务的过程就会造成不必要的时间浪费。所以,无限制的增加线程并不能一直增加程序的执行效率。
如何减少上下文的切换?
- 无锁并发编程。多线程竞争锁的时候,会引起上下文切换,所以多线程处理数据的时候,可以用一些办法来避免使用锁。
- CAS算法(Compare and Swap)。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
- 使用最少线程。避免不需要的线程。
- 协程。在单线程里实现多任务的调度算法,并在单线程里维持多个任务的切换。
死锁
并发编程过程中,由于多个线程间,共同抢占对方需要而又没有及时释放掉的资源,很容易导致死锁的情况发生。
面试的时候偶尔也会遇到,让手写一个死锁的情况。
public class DeadLockDemo {
privat static String A = "A";
privat static String B = "B";
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable()) {
@Override
public void run() {
sychronized(A) {
try {
Thread.currentThread().sleep(2000);
}catch (InterruptedException e) {
e.printStackTrace();
}
sychronized(B) {
System.out.println("1");
}
}
}
});
Thread t2 = new Thread(new Runnable()) {
@Override
public void run() {
sychronized(B) {
sychronized(A) {
System.out.println("2");
}
}
}
});
t1.start();
t2.start();
}
}
如何避免死锁呢?
- 避免一个线程同时获取多个锁。
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
- 对于数据库锁,加锁和解锁必须在一个数据库连接内,否则会出现解锁失败。