说到并发编程,首先应该想到一个问题:我们都知道,使用并发目的肯定是使程序运行的更快,但是,是不是使用了并发之后,程序的效率就真的上去了?在实际的并发操作时,我们都知道会产生错误,所以, 下面所阐述的内容也是我在学习之后总结的,也是希望自己在日后的学习中,不断的巩固并加强自身的功底。
1.1 性能问题
在进行多线程操作时,我们总认为所有的线程都是同时工作,其实不是这样的。实际中CPU通过时间片分配算法循环执行每条指令,只是每个时间段时间很短,所以我们的错觉是所有的线程同时运行。当任务在执行的时候,时间到了,CPU会切换到另外一个进程,但是,会保存当前程序的运行状态,以便下次返回到该程序继续运行的时候可以再加载该程序之前执行保存的状态。所以任务从保存到再加载的过程就是一次上下文切换。 如果并发量不是很大的时候,对CPU来讲压力不是很大,但当有大量的任务需要CPU不断进行上下文切换的操作时,会很消耗资源,影响性能。
如何减少上下文切换?
减少上下文切换的方法有:无锁并发编程、 CAS算法(Compare And Swap) 、使用最少线程、使用协程。
- 无锁并发编程:多线程竞争锁时,会引起上下午切换,所以多线程处理数据时,可以用一些办法来避免使用锁,例如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据
- CAS算法:Java的Atomic包使用CAS算法来更新数据,而不许要进行加锁。具体的可见:https://blog.csdn.net/v123411739/article/details/79561458 这个文章感觉对CAS将的蛮透彻。
- 使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,会造成大量线程处于等待状态。
- 尽可能使用协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。具体关于协程的概念可以参考:http://www.sohu.com/a/237171690_465221
以上是采用并发操作系统所产生的性能上面的问题。但是还会有另外一个问题存在:死锁。
1.2 产生死锁
锁是个经常使用的工具,用起来相对于比较简单,但是使用锁的时候会产生一个问题:产生死锁。
如下面的代码实例:
/*
程序就是简单实现有两个线程在t1、t2在使用同步锁时,产生死锁测试
运行结果是:有的时候会运行正常,有的时候进程一直卡在那不动。
*/
package ThreadTest;
public class DeadLockTest {
private static String A = "A";
private static String B = "B";
private void deadLock() {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(A) {
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println("t1");
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(B) {
synchronized (A) {
System.out.println("t2");
}
}
}
});
t1.start();
t2.start();
}
public static void main(String[] args) {
new DeadLockTest().deadLock();
}
}
避免产生死锁的几个常见的方法:
- 避免一个线程同时获得多个锁。
- 避免一个线程在一个锁内同时占用多个资源,尽量保证一个锁只占一个资源。
- 尝试使用定时锁,使用Lock.tryLock(timeout)来替代使用内部锁机制,避免出现死锁时,程序卡死。
- 对于数据库锁,加锁和解锁必须在一个数据库里,否则会出现解锁失败的情况。
总结:这一章主要讲了在考虑并发问题时,我们主要会考虑到的两个问题:如何降低CPU上下文切换、如何避免死锁,在实际开发中,我们应该注意到这些地方。