java并发编程知识体系详解篇四(并发编程的挑战篇)

上下文切换

单核的处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现多线程执行的机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。

比如说我们正在读一本书,但是这个时候你的妈妈叫你吃饭了,那你得放下手中的书去吃饭是不是(我试过没听我妈的话,被骂了好久,莫尝试!)。那这个时候你得记住你看的书看到了第几页第几行,当你吃完饭回来再接着看书。但这个时候你再看书,是不是会因为刚刚吃完饭了,所以会对你读书的专心程度有影响。这就是上下文切换,同样,上下文切换会影响多线程的执行速度。

实例代码测试:

public class ConcurrencyTest {
    
    

    private static final long count = 100000001;

    // 并发执行
    private static void concurrency() throws InterruptedException {
    
    
        long start = System.currentTimeMillis();
        Thread thread = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                int a = 0;
                for (int i = 0; i < count; i++) {
    
    
                    a+=5;
                }
            }
        });
        thread.start();
        int b = 0;
        for (long i = 0; i < count; i++) {
    
    
            b--;
        }
        long time = System.currentTimeMillis() - start;
        thread.join();
        System.out.println("concurrency : "+time+"ms,b = "+b);
    }

    // 串行执行
    private static void serial(){
    
    
        long start = System.currentTimeMillis();
        int a = 0;
        for (int 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("concurrency : "+time+"ms,b = "+b+",a = "+a);
    }
    public static void main(String[] args) throws InterruptedException {
    
    
        concurrency();
        serial();
    }
}

在这里插入图片描述
这是串行和并行的执行数据,不同的电脑不同的配置,所得到的数据可能不一样。当数据量比较小的时候为什么串行的代码比并发执行的代码要快?因为并发执行的java程序在运行的过程中有线程的创建和上下文切换的开销。不过可以得出这样的一个结论:并发执行的代码不一定比串行执行的代码快。

减少上下文切换的方法

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

死锁

死锁代码演示

public class DeadLockDemo {
    
    

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

    public static void main(String[] args) {
    
    
        new DeadLockDemo().deadLock();
    }
    private void deadLock(){
    
    
        Thread t1 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                synchronized (A){
    
    
                    try{
    
    
                        Thread.sleep(2000);
                    } catch (InterruptedException e){
    
    
                        e.printStackTrace();
                    }
                    synchronized (B){
    
    
                        System.out.println("123");
                    }
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                synchronized (B){
    
    
                   synchronized (A){
    
    
                       System.out.println("234");
                   }
                }
            }
        });
        t1.start();
        t2.start();
    }
}

资源限制的挑战

什么是资源限制?

资源限制是指在进行并发编程时,程序的执行速度受限于计算机硬件资源或软件资源。

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

资源限制引起的问题

在并发编程中,将代码执行速度加快的原则是将代码中串行执行的部分变成并发执行,但是如果将某段串行的代码并发执行,因为受限于资源,仍然在串行执行,这时候程序不仅不会加快执行,反而会更慢,因为增加了上下文切换和资源调度的时间。例如,一个java程序需要调用CPU和存储器,网络设备,一句话说吧:利用多线程实现同时下载多个目标,然后计算机受限于网络速度,计算机存储速度,计算机CPU的处理速度,这个时候往往需要好几个小时都无法完成的一个多线程任务,但是单线程的话,一个多小时就可以完成任务了。

如何有效解决资源限制问题

硬件资源: 现在考虑使用集群并行执行程序,因为单机的资源有限制。
软件资源: 考虑使用资源池将资源复用。比如使用连接池将数据库和Socket连接复用,或者在调用对方webservice接口获取数据时,只建立一个连接。

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

猜你喜欢

转载自blog.csdn.net/m0_46198325/article/details/120806117