一个程序内部能拥有多个线程并行执行。一个线程的执行能够被认为是一个CPU在执行该程序。当一个程序运行在多线程下,就好像有多个CPU在同时执行该程序。这原本是件好事,多线程执行,大大提升了咱们的程序运行效率。
但使人头疼的多线程安全问题,也随之而来。若是一个线程在读一个内存时,另外一个线程正向该内存进行写操做,那进行读操做的那个线程将得到什么结果呢?是写操做以前旧的值?仍是写操做成功以后的新值?或是一半新一半旧的值?
或者,若是是两个线程同时写同一个内存,在操做完成后将会是什么结果呢?** 是第一个线程写入的值?仍是第二个线程写入的值?仍是两个线程写入的一个混合值?
所以如没有合适的预防措施,任何结果都是可能的。并且这种行为的发生甚至不能预测,因此结果也是不肯定性的。
因此综合而言,学习并发编程有最重要的2点。
一、理解并发编程原理,便于咱们更好的提升CPU的使用率,加快任务执行速度,下降系统响应时间;
二、运用好并发编程能帮咱们很好地解决多线程安全问题;
复制代码
并发编程在必定程度上离不开多核CPU的发展。随着单核CPU的研发已经不能遵循“摩尔定律”(摩尔定律是硬件发展的观测定律,另外还有基于“摩尔定律”的“反摩尔定律”,不过“反摩尔定律”是软件领域的定律,有兴趣的能够自行了解),硬件工程师们为了进一步提高计算速度,而不是再追求单独的计算单元,而是将多个计算单元整合到了一块儿,也就是造成了多核CPU。短短十几年的时间,家用型CPU,好比Intel i7就能够达到4核心甚至8核心。而专业服务器则一般能够达到几个独立的CPU,每个CPU甚至拥有多达8个以上的内核。算法
所以,“摩尔定律”彷佛在CPU核心扩展上继续获得体验。而在多核的CPU的背景下,催生了并发编程的趋势,通并发编程的形式能够将多核CPU的计算能力发挥到极致,性能获得提高。数据库
在特殊的业务场景下先天的就适合于并发编程。好比在图像处理领域,一张1024X768像素的图片,包含达到78万6千多个像素。即时将全部的像素遍历一边都须要很长的时间,面对如此复杂的计算量就须要充分利用多核的计算的能力。编程
另外在开发购物平台时,为了提高响应速度,须要拆分,减库存,生成订单等等这些操做,就能够进行拆分利用多线程的技术完成。面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程吻合更能这种业务拆分正是由于这些优势,使得多线程技术可以获得重视,也是一名CS学习者应该掌握的:安全
充分利用多核CPU的计算能力;bash
方便进行业务拆分,提高应用性能服务器
2.1 频繁的上下文切换 时间片是CPU分配给各个线程的时间,由于时间很是短,因此CPU不断经过切换线程,让咱们以为多个线程是同时执行的,时间片通常是几十毫秒。多线程
每次切换时,须要把当前的状态保存起来,以便可以进行恢复先前状态,而这个切换行为很是损耗性能,过于频繁切换反而没法发挥出多线程编程的优点。一般减小上下文切换能够采用无锁并发编程、 CAS算法、使用最少的线程和使用协程。并发
无锁并发编程:能够参照的ConcurrentHashMap锁分段的思想,不一样的线程处理不一样段的数据,这样在多线程竞争的条件下,能够减小上下文切换的时间。app
CAS算法,利用原子下使用CAS算法来更新数据,使用了乐观锁,能够有效的减小一部分没必要要的锁竞争带来的上下文切换异步
使用最少线程:避免建立不须要的线程,好比任务不多,可是建立了不少的线程,这样会形成大量的线程都处于等待状态
协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
因为上下文切换是个相对比较耗时的操做,因此在 “Java的并发编程的艺术” 一书中有过一个实验,并发累加未必会比串行累加速度快。
2.2 线程的安全性问题
多线程编程中最难以把握的就是临界区线程安全问题,稍微不注意就会出现死锁的状况,一旦产生死锁就会形成系统功能不可用。
public class DeadLockDemo {
private static String demo_a = "A";
private static String demo_b = "B";
public static void main(String[] args) {
deadLock();
}
public static void deadLock() {
Thread thread_a = new Thread(new Runnable() {
@Override
public void run() {
synchronized (demo_a){
System.out.println("get demo a");
try {
Thread.sleep(3000);
synchronized (demo_b){
System.out.println("get demo b");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread_b = new Thread(new Runnable() {
@Override
public void run() {
synchronized (demo_b){
System.out.println("get demo b");
synchronized (demo_a){
System.out.println("get demo a ");
}
}
}
});
thread_a.start();
thread_b.start();
}
}
复制代码
在上面的这个demo中,开启了两个线程thread_a,thread_b,其中thread_a占用了demo_a,并等待被thread_b释放的资源demo_b,thread_b占用了资源demo_b正在等待被thread_a释放的资源demo_a。
所以thread_a,thread_b出现线程安全的问题,造成死锁。
一般能够用以下方式避免死锁的状况:
避免一个线程同时得到多个锁;
避免一个线程在锁内部占有多个资源,尽可能保证每一个锁只占用一个资源;
尝试使用定时锁,使用lock.tryLock(timeOut),当超时等待时当前线程不会阻塞;
对于数据库锁,加锁和解锁必须在一个数据库链接里,不然会出现解锁失败的状况
因此,如何正确的使用多线程编程技术有很大的学问,好比如何保证线程安全,如何正确理解因为JMM内存模型在原子性,有序性,可见性带来的问题,好比数据脏读,DCL等这些问题(在后续篇幅会讲述)。而在学习多线程编程技术的过程当中也会让你收获颇丰。 3. 须要了解的一些概念 3.1 同步VS异步
同步和异步一般用来形容一次方法调用。同步方法调用开始后,调用者必须等待被调用的方法结束后,调用者后面的代码才能执行。而异步调用,指的是,调用者不用管被调用方法是否完成,都会继续执行后面的代码,当被调用的方法完成后会通知调用者。
3.2 并发与并行
并发和并行是十分容易混淆的概念。并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。实际上,若是系统内只有一个CPU,使用多线程时,在真实系统环境下不能并行,只能经过切换时间片的方式交替进行,从而并发执行任务。真正的并行只能出如今拥有多个CPU的系统中。
3.3 阻塞和非阻塞
阻塞和非阻塞一般用来形容多线程间的相互影响,好比一个线程占有了临界区资源,那么其余线程须要这个资源就必须进行等待该资源的释放,会致使等待的线程挂起,这种状况就是阻塞,而非阻塞就刚好相反,它强调没有一个线程能够阻塞其余线程,全部的线程都会尝试地往前运行。
3.4 临界区
临界区用来表示公共资源或者说是共享数据,能够被多个线程使用。可是每一个线程使用时,一旦临界区资源被一个线程占有,那么其余线程必须等待。
Java并发系列一:什么是并发? blog.csdn.net/TzBugs/arti…
Java并发系列二:线程的建立、状态转换及基本操做 blog.csdn.net/tzbugs/arti…
Java并发系列三:Java内存模型以及happens-before规则 blog.csdn.net/tzbugs/arti…