您的位置:首页 > 编程语言 > Java开发

《Java并发编程的艺术》第一章——并发编程的挑战

2017-11-27 22:37 561 查看
并发编程的挑战

知识点:

上下文切换
死锁
资源限制的挑战
详解:
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 并发 编程 线程