《Java并发编程的艺术》第一章——并发编程的挑战
2017-11-27 22:37
561 查看
并发编程的挑战
知识点:
上下文切换
死锁
资源限制的挑战
详解:
1.上下文切换。
并发编程的目的是为了让程序运行得更快,但并不是启动越多的线程就能运行越快。因为会存在线程上下文切换的消耗。即使对于单核CPU来说,同样会存在上下文切换。(并不存在真正意义上的并行,只是CPU给线程分配执行的时间片来宏观实现并行,相对于CPU来说,都是串行执行的)。因为在上下文切换时,CPU会保存当前任务执行的状态,去执行另外一个任务,当当前任务执行下一个时间片时,需要恢复到上次的工作状态。这势必会产生资源消耗。
以下面程序为例:
执行结果:
【备注】:运行结果可能受电脑硬件、运算复杂程度等影响,但此例子足以说明上面的观点,大
家可以弄个复杂的运算来测试一下。
减少上下文切换的方法:
无锁并发编程:多线程在竞争锁的时,会引起上下文切换,可以使用一些方法避免使用锁。
CAS算法:Java Atomic包下使用CAS算法更新数据,而不需要加锁。(CAS会带来额外的问题,后续做出详解。)
使用最少线程:避免创建不需要的线程。
协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
2.死锁
锁是java中保证同步很重要的一个工具,但不正确的使用锁,可能会导致死锁。
如下例子:
在程序运行时,DeadLockTest-Thread-1线程先获取了lock1上的对象锁,然后在5秒之后去申请lock2的对象锁,但此时DeadLockTest-Thread-2线程已经获取到了lock2上的对象锁,在等待获取lock1的对象锁,于是,此时两个线程互相等待,造成了死锁。
【备注】:可能我的new Integer()引用命名为lock不太合适,但我觉得完全可以说明问题。
避免出现死锁的几个方法:
避免一个线程同时获取多个锁。
避免一个线程在锁内同时占用多个资源。
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
3.资源限制的挑战
什么是资源限制?
资源限制是指在进行并发编程时,程序的执行速度受限于计算机的硬件或软件资源。如:服务器带宽2Mb/s,下载速度1Mb/s,启动10个线程下载东西,速度不可能达到10Mb/s。所以在并发编程时要考虑到这些因素。
资源限制引发的问题
在并发编程时,提高程序运行速度的原则是把串行执行的部分变成并行执行,但如果受限于硬件资源,并行执行反而会变慢,因为增加了上下文切换的消耗。
如何解决资源限制的问题
对于硬件资源,可以考虑升级硬件或部署集群。
对于软件资源,可以使用资源池将资源进行复用及统一管理。
在资源限制情况下进行并发编程
如何在资源限制的情况下,让程序执行得更快?方法就是,根据不同的资源限制调整程序的并发度。
【备注】:若本文有错或不恰当的描述,请各位不吝斧正。谢谢!
知识点:
上下文切换
死锁
资源限制的挑战
详解:
1.上下文切换。
并发编程的目的是为了让程序运行得更快,但并不是启动越多的线程就能运行越快。因为会存在线程上下文切换的消耗。即使对于单核CPU来说,同样会存在上下文切换。(并不存在真正意义上的并行,只是CPU给线程分配执行的时间片来宏观实现并行,相对于CPU来说,都是串行执行的)。因为在上下文切换时,CPU会保存当前任务执行的状态,去执行另外一个任务,当当前任务执行下一个时间片时,需要恢复到上次的工作状态。这势必会产生资源消耗。
以下面程序为例:
package com.lipeng.first; import java.util.concurrent.CountDownLatch; public class ConcurrentTest { /** * 启动的线程数 */ private static int threadCount =10; /** * 循环次数 */ private static int loopCount=1000000; /** * 闭锁,用于获取线程全部执行完毕后的"事件" */ private static CountDownLatch donwLatch=new CountDownLatch(threadCount); private static int a=0; /** * 单线程执行 */ public static void serizal(){ long startTime=System.currentTimeMillis(); for(int i=0;i<loopCount;++i){ ++a; } long cost=System.currentTimeMillis()-startTime; System.out.println("单线程方法执行完毕,共消耗 "+cost+" ms"); } /** * 多线程执行 */ public static void concurrent(){ long startTime=System.currentTimeMillis(); for(int i=0;i<threadCount;++i){ Thread thread=new Thread(new Runnable() { @Override public void run() { int c=0; for(int i=0;i<loopCount/threadCount;++i){ ++a; } donwLatch.countDown(); } },"Thread-"+i); thread.start(); } try { donwLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } long cost=System.currentTimeMillis()-startTime; System.out.println("多线程("+threadCount+"个)方法执行完毕,共消耗 "+cost+" ms"); } public static void main(String[] args) { ConcurrentTest.serizal(); ConcurrentTest.concurrent(); } }
执行结果:
【备注】:运行结果可能受电脑硬件、运算复杂程度等影响,但此例子足以说明上面的观点,大
家可以弄个复杂的运算来测试一下。
减少上下文切换的方法:
无锁并发编程:多线程在竞争锁的时,会引起上下文切换,可以使用一些方法避免使用锁。
CAS算法:Java Atomic包下使用CAS算法更新数据,而不需要加锁。(CAS会带来额外的问题,后续做出详解。)
使用最少线程:避免创建不需要的线程。
协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
2.死锁
锁是java中保证同步很重要的一个工具,但不正确的使用锁,可能会导致死锁。
如下例子:
package com.lipeng.first; import java.util.concurrent.TimeUnit; public class DeadLockTest { public static Integer lock1=new Integer(1); public static Integer lock2=new Integer(1); public static void exec1(){ synchronized (lock1) { System.out.println("exec1 方法获取到lock1上的对象锁"); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (lock2) { System.out.println("exec1 方法获取到lock2上的对象锁"); } } } public static void exec2(){ synchronized (lock2) { System.out.println("exec2 方法获取到lock2上的对象锁"); synchronized (lock1) { System.out.println("exec2 方法获取到lock1上的对象锁"); } } } public static void main(String[] args) { Thread thread1=new Thread(new Runnable() { @Override public void run() { DeadLockTest.exec1(); } }); Thread thread2=new Thread(new Runnable() { @Override public void run() { DeadLockTest.exec2(); } }); thread1.start(); thread2.start(); } }通过VisualVM工具可以明显的看到此程序出现了死锁:
在程序运行时,DeadLockTest-Thread-1线程先获取了lock1上的对象锁,然后在5秒之后去申请lock2的对象锁,但此时DeadLockTest-Thread-2线程已经获取到了lock2上的对象锁,在等待获取lock1的对象锁,于是,此时两个线程互相等待,造成了死锁。
【备注】:可能我的new Integer()引用命名为lock不太合适,但我觉得完全可以说明问题。
避免出现死锁的几个方法:
避免一个线程同时获取多个锁。
避免一个线程在锁内同时占用多个资源。
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
3.资源限制的挑战
什么是资源限制?
资源限制是指在进行并发编程时,程序的执行速度受限于计算机的硬件或软件资源。如:服务器带宽2Mb/s,下载速度1Mb/s,启动10个线程下载东西,速度不可能达到10Mb/s。所以在并发编程时要考虑到这些因素。
资源限制引发的问题
在并发编程时,提高程序运行速度的原则是把串行执行的部分变成并行执行,但如果受限于硬件资源,并行执行反而会变慢,因为增加了上下文切换的消耗。
如何解决资源限制的问题
对于硬件资源,可以考虑升级硬件或部署集群。
对于软件资源,可以使用资源池将资源进行复用及统一管理。
在资源限制情况下进行并发编程
如何在资源限制的情况下,让程序执行得更快?方法就是,根据不同的资源限制调整程序的并发度。
【备注】:若本文有错或不恰当的描述,请各位不吝斧正。谢谢!
相关文章推荐
- 《Java并发编程的艺术》--第一章--并发编程的挑战
- Java并发编程的艺术-第一章之并发编程的挑战
- 第一章-并发编程的挑战
- Java并发编程的艺术-第一章<并发编程的挑战>
- 那些年读过的书《Java并发编程的艺术》一、并发编程的挑战和并发机制的底层实现原理
- 并发编程的艺术笔记-(第一章)并发编程的挑战
- 第一章 并发编程的挑战
- 第一章 并发编程的挑战
- 第一章 并发编程的挑战 总结
- 《java并发编程的艺术》---第一章:并发编程的挑战
- 并发编程的挑战
- Java并发(二)—— 并发编程的挑战 与 并发机制的底层原理
- 多线程之:并发编程面临的挑战
- OC中并发编程的相关API和面临的挑战
- JAVA并发编程艺术 一(并发编程的挑战)
- java并发编程学习(一) 并发编程的挑战
- java并发编程(一)--并发编程的优缺点与挑战
- 挑战编程技能(第一章)
- Java7并发编程指南——第一章:线程管理
- [Java 并发] Java并发编程实践 思维导图 - 第一章 简单介绍