Java并发编程的挑战

首先什么是并发编程?

并发编程是指在相同时间间隔内,多个事件按照一定的顺序进行。比如说你在吃饭的整个过程中,吃了米饭、吃了蔬菜、吃了牛肉。吃米饭、吃蔬菜、吃牛肉这三件事其实就是并发执行的。

并发编程的目的是为了程序运行得更快,但是并不是启动更多的线程就能让程序最大限度地并发执行。在并发编程过程中,如果希望程序运行得更快,就会面临着非常多的挑战,比如上下文切换,死锁问题,以及受限于硬件和软件的资源问题,接下来对这几个问题进行详细的介绍。

上下文切换问题

在单核处理器中,CPU给每个线程分配CPU时间片来实现多线程并发,在切换到下个线程之前会保存当前线程的任务状态,下次再切换回这个任务时可以加载这个任务的状态,任务从保存到加载的过程就是一次上下文切换,在多线程编程中,由上下文切换造成的损耗是会影响线程的执行效率的。

上面我们讲到上下文切换存在效率的损耗,所以,大家能够接受多线程不一定会比单线程快。下面我们以一段简单的代码为测试例子,一万次,百万,千万,一亿,十亿次测试:

public class ConcurrencyTest {
    private static final long count = 100000000l;

    public static void concurrency() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread thread = new Thread(() -> {
            int a = 0;
            for (long i = 0; i < count; i++) {
                a += 5;
            }
        });

        thread.start();

        int b= 0 ;
        for (long i = 0; i <count ; i++) {
            b--;
        }
        thread.join();
        long time = System.currentTimeMillis()-start;
        System.out.println("concurrency:"+time+"ms,b="+b);
    }

    private static void serial(){
        long start = System.currentTimeMillis();
        int a=0;
        for (long i = 0; i <count ; i++) {
            a+=5;
        }
        int b= 0 ;
        for (long i = 0; i <count ; i++) {
            b--;
        }
        long time = System.currentTimeMillis()-start;
        System.out.println("serial:"+time+"ms,b="+b);
    }

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

一万次:
concurrency:60ms,b=-10000
serial:0ms,b=-10000

百万次:
concurrency:83ms,b=-1000000
serial:5ms,b=-1000000

千万次:
concurrency:70ms,b=-10000000
serial:12ms,b=-10000000

一亿次:
concurrency:98ms,b=-100000000
serial:85ms,b=-100000000

十亿次:
concurrency:462ms,b=-1000000000
serial:734ms,b=-1000000000

上面测试结果显示,当一个简单的计算只要结果大于十亿次并发累加才会比串行快。

那么怎么减少上下文切换呢?

  • 无锁编程。多线程竞争锁时会引起上下文切换,所以多线程处理数据时可以用一些办法来避免使用锁,如将数据的ID按照hash算法取模,不同的线程处理不同的数据。
  • CAS算法。java的Atomic包使用的CAS算法来更新数据,而不需要加锁。
  • 使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。
  • 协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

死锁问题

锁是非常有用的工具,运用场景非常多,因为它使用起来非常简单,而且易于理解。但是它同时也会带来一些困扰,那就是可能会引起死锁,一旦产生死锁,就会造成系统功能不可用。比如以下代码就是造成两个线程相互等待,程序无法结束。

public class DeadLock {

    private static  String A = "A";
    private static String B = "B";

    public static void main(String[] args) {
        new DeadLock().deadLock();
    }

    private void deadLock(){
        Thread t1 =  new Thread(()->{
            synchronized (A){
                try {
                    Thread.currentThread().sleep(2000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B){
                    System.out.println("1");
                }
            }
        });

        Thread t2 =  new Thread(()->{
            synchronized (B){
                try {
                    Thread.currentThread().sleep(2000);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (A){
                    System.out.println("2");
                }
            }
        });

        t1.start();
        t2.start();
    }
}

一旦出现问题,程序就会卡死在那里,只能通过dump线程查找原因。

那么怎么避免死锁呢,有以下几点方法:

  • 避免一个线程同时获得多个锁。
  • 避免一个线程在所内同时占用资源,保证每个锁只占用一个资源。
  • 尝试使用定时锁,使用Lock.tryLock(timeout)来替换使用内部锁机制。
  • 对于数据库,加锁和解锁必须在一个数据库连接里,否则就会出现解锁失败的情况。

资源限制的挑战

什么是资源限制?

资源限制是指在进行并发编程时,程序的执行速度受限于计算机硬件资源或者软件资源。例如,服务器的带宽只有2Mb/s,某个资源的下载速度是1Mb/s,系统启动10个线程下载资源,下载速度不会变成10Mb/s,所以在进行并发编程时,要考虑这些资源的限制。硬件资源有带宽的上传和下载速度,硬盘的读写速度和CPU的处理速度。软件资源限制有数据库连接数和socket连接数等。

资源限制引发的问题

比如一段代码,原本是串行执行的,如果将某段串行的代码并发执行,因为受限于资源,仍在串行执行,这时候程序不仅不会加快执行,反而会更慢,因为增加了上下文切换和资源调度的时间。例如,使用多线程下载资源,有时候不如单线程下载快。

如何解决资源限制的问题

对于硬件资源,可以考虑集群化部署,对于软件资源,可以考虑资源池将资源复用,比如使用连接处将数据库和Sockert连接复用,或者在调用对方webservice接口数据时,只建立一个连接。

在资源限制的情况下进行并发编程

根据不同的资源限制调整程序的并发度,比如在下载文件程序依赖两个资源:带宽和硬盘读写速度。有数据库操作,涉及数据库连接数,如果SQL语句执行非常快,而线程的数量比数据库连接大很多,则某些线程会被阻塞,等待数据库连接。

小结

在上面介绍了什么是并发编程,以及并发编程带来的挑战以及一些应对策略,在这里推荐大家多使用jdk的并发包提供的容器和工具类来解决并发问题,因为这些类都已经充分的测试好优化,可以避免很多问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值