同步VS异步
同步和异步用来形容一次方法调用。
同步:同步方法调用一开始,调用者必须等待被调用的方法结束后,调用者后面的代码才能执行。
异步:异步调用指的是,调用者不管被调用的方法是否完成,都会继续执行后面的代码,当被调用的方法完成后会通知调用者。
并发VS并行
并发:指多个任务交替执行
并行:真正意义上的“同时运行”。
实际上,系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。
阻塞VS非阻塞
阻塞和非阻塞通常用来形容多线程间的相互影响。
阻塞:一个线程占有了临界区资源,那么其他线程需要这个资源就必须进行等待该资源的释放,会导致等待的线程挂起。
非阻塞:没有一个线程可以阻塞其他线程,所有的线程都会尝试地前行。
临界区
一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。
并发编程
优点:
1、并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。
2、面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分。
缺点:并发编程的目的是为了让程序运行的更快,但是也会造成一些问题:上下文切换,线程安全
上下文切换
即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。因为时间片非常短,所以CPU通过不断地切换线程执行,让我们感觉多个线程是同时执行的。
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
多线程一定快吗?
不一定,因为线程有创建和上下文切换的开销。
如何减少上下文切换?
1、无锁并发编程。可以参考concurrentHashMap锁分段的思想,不同线程处理不同段的数据,这样在多线程竞争条件下,可以减少上下文切换的时间。
2、CAS算法。利用Atomic下使用CAS算法来更新数据,使用了乐观锁,可以有效减少一部分不必要的锁竞争带来的上下文切换。
3、使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多的线程,这样会造成大量的线程都处于等待状态。
4、协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
死锁
一旦产生死锁,就会造成系统功能的不可用。
模拟死锁的代码:
public class DeadLockDemo {
private static String A = "A";
private static String B = "B";
public static void main(String[] args){
new DeadLockDemo().deadLock();
}
private void deadLock(){
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
//对A加锁
synchronized(A){
System.out.println("t1 get resource A");
try {
//睡眠一段时间,让t2先获取到B
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
//尝试获取B,对B加锁,因为t2一直在尝试获取A,一直不释放B,所以t1获取不到B
synchronized(B){
System.out.println("t1 get resource B");
System.out.println("1");
}
}
}
});
Thread t2 =new Thread(new Runnable() {
@Override
public void run() {
//对B加锁
synchronized (B){
System.out.println("t2 get resource B");
//获取A,对A加锁,因为t1一直在尝试获取B,没有获取到一直未执行结束,不释放A,所以t2不会获取到A
synchronized(A){
System.out.println("t2 get resource A");
}
}
}
});
t1.start();
t2.start();
}
}
避免死锁常用的方法:
1、避免一个线程同时获取多个锁。
2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
3、尝试使用定时锁,使用lock.tryLock(timeout)来代替使用内部锁机制。
4、对数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。