Java并发编程学习|第1篇:并发编程介绍

前言

最近一直在研究Java的并发编程,在此做一下学习总结。因为水平有限,所以文章中会有些地方直接照搬书上的概念,或者借鉴其他文章中的概念,引用到的书籍或者文章会在文中注明。如有侵权,请及时联系我删除,感谢。

 

什么是并发编程

什么是并发编程?我们看一下百度百科的概念:

所谓并发编程是指在一台处理器上“同时”处理多个任务。并发是在同一实体上的多个事件。多个事件在同一时间间隔发生

 在单线程程序中,程序的执行都是线性执行的。现在试想一种情况:你经营了一个快递站点,站点里有很多个快递需要配送,站点里只有一个快递员去配送快递,效率非常低,可能一天下来快递也没配送完,那么你会如何解决这个问题呢?

这个问题的结果显而易见:多招几个快递员送快递就行了。在这个模拟的场景里,配送快递就是需要执行的任务,而快递员就是执行这个任务的线程。所谓的并发编程就是使用多个线程去并行的执行我们的任务,提升我们程序的执行速度。

那么,是不是用了多线程去执行任务,效率就一定会提升呢?不一定。

 

并发编程带来的问题

1、上下文切换

即使是单核的处理器,也是支持多线程执行任务的,CPU会给每个线程分配时间片来执行,每个线程执行一段时间,由CPU不停的进行切换线程,让用户感觉线程是并行执行的。在切换前,处理器会记录下线程的执行状态,在下一次切换回来时,会将原先的状态加载回来,这个切换的过程就叫做上下文切换。

从上下文切换的概念,我们可以理解到,线程的并行是由处理器不停的切换来实现的,那么如果一个任务的执行量不是那么大时,多线程并不一定会比单线程快,因为单线程没有线程间不停切换的开销。

2、死锁

死锁在多线程开发中,是个十分致命的问题。上下文切换只会影响效率,而死锁会导致线程无限的等待,死锁时间长的话,甚至可能导致应用程序的崩溃。那么什么是死锁呢?以一段代码为例。

public class DeadLock {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public static void main(String[] args) {
        Thread task1 = new Thread(new Task1());
        Thread task2 = new Thread(new Task2());
        task1.start();
        task2.start();
    }

    static class Task1 implements Runnable{

        @Override
        public void run() {
            //获取lock1
            synchronized (lock1){
                try {
                    //线程睡眠1s再获取锁
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //获取lock2
                synchronized (lock2){
                    System.out.println("task1 get lock2");
                }
            }
        }
    }

    static class Task2 implements Runnable{

        @Override
        public void run() {
            //获取lock2
            synchronized (lock2){
                try {
                    //线程睡眠1s再获取锁
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //获取lock1
                synchronized (lock1){
                    System.out.println("task2 get lock1");
                }
            }
        }
    }
}

如代码所示,task1在获取lock1之后尝试获取lock2,而此时lock2已被task2获取了,task2也在等待lock1。这就造成了task1和task2互相等待对面释放锁的情况,这种情况就叫做死锁。

3、资源限制

在使用多线程的时候,不能盲目追求效率,而忽略资源的限制,资源限制主要分为两种:

(1) 硬件资源

硬件资源指的是服务器的带宽、CPU的处理速度、硬盘的读写速度等

(2) 软件资源

软件资源指的是数据库连接、Redis连接、Http连接等

 

笔者在使用多线程开发一个接口时,曾碰到过资源耗尽的情况,在此做个分享:

该接口在执行过程中,需调用数次第三方接口、Redis、mysql入库,并且这是个批量操作,可能会执行多次。最一开始使用单线程执行,在批量执行的次数少的时候,效率并不会用什么影响,但是上生产环境之后,批量执行的量变多,效率开始变慢,就考虑使用多线程去解决问题。最一开始使用了50个线程在笔者的笔记本上调试,cpu很快飙升至95+%,后来逐渐减少线程数,发现调用第三方接口时,经常等待http连接超时的情况。因为http连接池中的连接数量有限,所以很多线程在等待线程时超时。于是继续减少线程数,并且将接口的响应方式从同步改成异步,最终才解决了问题。

笔者碰到的问题就出现了硬件限制与软件限制,所以衡量资源的限制在多线程开发中也是件很重要的事情。

 

总结

本篇文章介绍了并发编程的概念以及并发编程会带来的问题。在开发中使用多线程之前,一定要衡量使用多线程的风险,虽然多线程在大多数时候会提升程序的执行速度,但也会带来相应的问题,并提升开发和调试时的复杂度。有些场景下可能并不需要使用多线程,比如一些不需要及时得到响应的场景下,完全可以使用异步获取结果的方式去单线程执行任务。如果需要使用多线程开发,建议使用Java的concurrent包下的容器和工具。

 

参考资料

《并发编程的艺术》 --方腾飞、魏鹏、程晓明

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值