并发编程的挑战

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

1.上线文切换      

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算法、使用最少线程和使用协程。

2.死锁

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

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)来替代使用内部锁机制。
  • 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。           
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值