【并发】并发编程的挑战

版权声明:版权所有,转载请标明链接。 https://blog.csdn.net/happyniceyq/article/details/80869454
1. 上下文切换
a. 概念:
i. CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换
b. 举例:读英文书遇到不会的单词查单词,记录所读书的页数
c. 多线程一定快吗?
i. 代码 package com.multithread;/*
* Created by 杨倩
* @auther 杨倩
* @DESCRIPTION ${DESCRIPTION}
* @create 2018/6/12
*/

public class ConcurrencyTest {

    private static final long count = 10000l;

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

    private static void concurrency() throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                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--;
        }
        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 (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 + ",a=" + a);
    }

}
console concurrency :1ms,b=-10000
serial:0ms,b=-10000,a=50000
测试结果
        
结论 当并发执行累加操作不超过百万次时,速度会比串行执行累加操作要
慢。那么,为什么并发执行的速度会比串行慢呢?这是因为线程有创建和上下文切换的开销。
d. 测试上下文切换次数和市场
i. 使用Lmbench3[1]可以测量上下文切换的时长。
ii. 使用vmstat可以测量上下文切换的次数
1) 示例
a) 
2) 结论
a) 上下文每1秒切换1000多次
e. 如何减少上下文切换
i. 无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。
ii. CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
iii. 使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。
iv. 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
f. 减少上下文切换实战
i. 通过减少线上大量WAITING的线程
ii. 例子 第一步:用jstack命令dump线程信息,看看pid为3117的进程里的线程都在做什么。
sudo -u admin /opt/ifeve/java/bin/jstack 31177 > /home/tengfei.fangtf/dump17
第二步:统计所有线程分别处于什么状态,发现300多个线程处于WAITING(onobjectmonitor)状态。
[tengfei.fangtf@ifeve ~]$ grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'
| sort | uniq -c
39 RUNNABLE
21 TIMED_WAITING(onobjectmonitor)
6 TIMED_WAITING(parking)
51 TIMED_WAITING(sleeping)
305 WAITING(onobjectmonitor)
3 WAITING(parking)
第三步:打开dump文件查看处于WAITING(onobjectmonitor)的线程在做什么。发现这些线
程基本全是JBOSS的工作线程,在await。说明JBOSS线程池里线程接收到的任务太少,大量线
程都闲着。
"http-0.0.0.0-7001-97" daemon prio=10 tid=0x000000004f6a8000 nid=0x555e in
Object.wait() [0x0000000052423000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000007969b2280> (a org.apache.tomcat.util.net.AprEndpoint$Worker)
at java.lang.Object.wait(Object.java:485)
at org.apache.tomcat.util.net.AprEndpoint$Worker.await(AprEndpoint.java:1464)
- locked <0x00000007969b2280> (a org.apache.tomcat.util.net.AprEndpoint$Worker)
at org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1489)
at java.lang.Thread.run(Thread.java:662)
第四步:减少JBOSS的工作线程数,找到JBOSS的线程池配置信息,将maxThreads降到
100。
<maxThreads="250" maxHttpHeaderSize="8192"
emptySessionPath="false" minSpareThreads="40" maxSpareThreads="75"
maxPostSize="512000" protocol="HTTP/1.1"
enableLookups="false" redirectPort="8443" acceptCount="200" bufferSize="16384"
connectionTimeout="15000" disableUploadTimeout="false" useBodyEncodingForURI= "true">
第五步:重启JBOSS,再dump线程信息,然后统计WAITING(onobjectmonitor)的线程,发现
减少了175个。WAITING的线程少了,系统上下文切换的次数就会少,因为每一次从
WAITTING到RUNNABLE都会进行一次上下文的切换。读者也可以使用vmstat命令测试一下。
[tengfei.fangtf@ifeve ~]$ grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'
| sort | uniq -c
44 RUNNABLE
22 TIMED_WAITING(onobjectmonitor)
9 TIMED_WAITING(parking)
36 TIMED_WAITING(sleeping)
130 WAITING(onobjectmonitor)
1 WAITING(parking 

2. 死锁
a. 例子 package com.multithread;/*
* Created by 杨倩
* @auther 杨倩
* @DESCRIPTION ${DESCRIPTION}
* @create 2018/6/12
*/

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.currentThread().sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (B) {
                        System.out.println("1");
                    }
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (B) {
                    synchronized (A) {
                        System.out.println("2");
                    }
                }
            }
        });
        t1.start();
        t2.start();
    }

}
b. 避免死锁
i. 避免一个线程同时获取多个锁。
ii. ·避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
iii. ·尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
iv. ·对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况


3. 资源限制的挑战
a. 什么是资源限制
i. 资源限制是指在进行并发编程时,程序的执行速度受限于计算机硬件资源或软件资源
ii. 硬件资源限制:带宽的上传/下载速度、硬盘读写速度和CPU的处理速度
iii. 软件资源限制有数据库的连接数和socket连接数
iv. 举例:服务器的带宽只有2Mb/s,某个资源的下载速度是1Mb/s每秒,系统启动10个线程下载资源,下载速度不会变成10Mb/s
b. 引发的问题
i. 在并发编程中,将代码执行速度加快的原则是将代码中串行执行的部分变成并发执行,但是如果将某段串行的代码并发执行,因为受限于资源,仍然在串行执行,这时候程序不仅不会加快执行,反而会更慢,因为增加了上下文切换和资源调度的时间。例如,之前看到一段程序使用多线程在办公网并发地下载和处理数据时,导致CPU利用率达到100%,几个小时都不能运行完成任务,后来修改成单线程,一个小时就执行完成了
c. 解决
i. 硬件资源限制:使用集群并行执行程序
ii. 软件资源限制,可以考虑使用资源池将资源复用
d. 在资源限制情况下进行并发编程
i. 如何在资源限制的情况下,让程序执行得更快呢?
1) 根据不同的资源限制调整程序的并发度,比如下载文件程序依赖于两个资源——带宽和硬盘读写速度。
2) 有数据库操作时,涉及数据库连接数,如果SQL语句执行非常快,而线程的数量比数据库连接数大很多,则某些线程会被阻塞,等待数据库连接。




猜你喜欢

转载自blog.csdn.net/happyniceyq/article/details/80869454