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

并发编程的挑战

2018-03-04 17:13 218 查看
      并发编程的目的是为了让程序运行的更快,但是,并不是启动更多的线程就能让程序最大限度的并发执行。在进行并发编程的时候,如果希望通过多线程执行任务让程序运行的更快,会面临非常多的挑战,比如:上下文切换的问题、死锁的问题以及受限于硬件和软件资源限制问题。

上线文切换      

CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文的切换。因此,并发执行速度不一定会比串行执行的操作快,这是因为线程有创建和上下文切换的开销,可以使用Linux命令vmstat测量上下文切换到次数。看如下的代码实例:public class ConcurrentTest {
public static long endTag = 10L;
public static int result = 0;

public static void main(String[] args) throws InterruptedException {
concurrent();
serial();
}

public static void concurrent() throws InterruptedException {
long startTime = System.currentTimeMillis();
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < endTag; i++) {
result += 5;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
int b = 0;
for (long i = 0L; i < endTag; i++) {
b--;
}
thread.join();
long costTime = System.currentTimeMillis() - startTime;
System.out.println("concurrent:" + costTime);
}

public static void serial() throws InterruptedException {
long startTime = System.currentTimeMillis();
for (int i = 0; i < endTag; i++) {
result += 5;
Thread.sleep(1);
}
int b = 0;
for (long i = 0L; i < endTag; i++) {
b--;
}
long costTime = System.currentTimeMillis() - startTime;
System.out.println("serial:" + costTime);
}
}
      上述代码的输出结果如下,结果表明使用串行化执行的速度要高于使用并发执行的速度。concurrent:14
serial:12 减少上下文切换的方法主要有:无锁并发编程、CAS算法、使用最少线程和使用协程。


死锁

      锁是一个非常有用的工具,
4000
运用的场景非常多,但同时它也会带来一些困扰,那就是有可能会引起死锁,一旦产生死锁,就会造成系统功能不可用。例如下面的实例代码:public class DeadLockDemo {
private static String A = "A";
private static String B = "B";

private void deadLock() {
final Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (A) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println("线程1");
}
}
}
});

final Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (B) {
synchronized (A) {
System.out.println("线程2");
}
}
}
});

thread1.start();
thread2.start();
}

public static void main(String[] args) {
new DeadLockDemo().deadLock();
}
}      一旦出现死锁,业务是可以感知的,因为不能继续提供服务了,那么只能通过dump线程查看到底是哪个线程出现了问题,例如使用jstack命令输出下面的dump日志,从日志中可以看到,代码发生了线程死锁:
       


     避免死锁的几个常用的方法如下:

避免一个线程同时获取多个锁。
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。           
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: