多线程存在的问题

众所周知,并发编程的目的是为了提高程序的效率。但是也不是说只要启动更多的线程就能让程序最大限度的并发执行来提高效率。在并发编程的道路上会有很多挑战,比如上下文切换,死锁,以及硬件和软件的资源限制等。下面我们就来了解和解决(避免)此类问题的发生。

上下文切换

在最早的单核处理器的时代,也支持多线程执行代码。CPU通过给每个线程分配CPU时间片来实现这个机制。假如现在有两个线程t1和t2来并发执行任务。当线程t1任务执行还未执行完,此时时间片用完了。CPU会把时间分配给t2来执行。也就是这种切换的过程称为上下文切换。因为每次分配的时间片的时间很短,所以需要不停的进行上下文切换来完成任务。但是这样频繁的上下文切换也是会消耗性能的,从而影响多线程的执行速度。
多线程一定快吗?不一定的。为什么说不一定,如下面的代码:

package com.swh;

public class Test {
    public static int count = 10000;
    public static void main(String[] args) throws InterruptedException {

        concurrency();

        serial();
    }

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

    }

    private static void concurrency() throws InterruptedException {
        long start = System.currentTimeMillis();

        Thread thread = new Thread(() -> {
            int a = 0;
            for (int i = 0; i < count; i++) {
                a++;
            }
        });
        thread.start();
        int b = 0;
        for (int i = 0; i <count; i++) {
            b--;
        }
        thread.join();
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }

}

经过测试:
循环次数 串行执行耗/ms 并行执行耗时 并发比串行快多少
count=1亿 130 77 约1倍
count=1千万 18 9 约1倍
count=1百万 5 5 差不多
count=10万 4 3 差不多
count=1万 0 1 慢
由上面的代码测试可知(由于每个机器的硬件资源及环境不一样所以我的测试数据不一定和读者的测试数据一致),当并发执行累加操作不超过百万次时,速度会比串行慢或差不多。为什么并发会比串行慢呢?这是因为线程有创建和上下文切换的开销。我们可以通过使用Lmbench3来测量上下文切换的时长,可以通过使用java自带的vmstat测量上下文切换的次数。(有兴趣的读者可以去研究一下)。

如何减少上下文的切换

减少上下文切换的方法有无锁并发编程,CAS算法,使用最少线程和使用协程。

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

减少上下文实战

减少上下文切换的思路:通过减少线上大量的WAITINA状态的线程来减少上下文的切换。
第一步: 使用jstack命令dump线程信息,看看java进程中的线程都是什么状态
Sudo -u admin /opt/java/bin/jstack pid > /opt/dump
/opt/java/bin/ 是java jd安装的路径
pid 是java进程id
/opt/dump 存放栈信息的目录
第二步:统计所有线程分别处于什么状态
grep java.lang.Thread.State dump | awk ‘{print 2345}’ | sort | uniq -c
39 RUNNABLE
21 TIMED_WAITING(onobjectmonitor)
6 TIMED_WAITING(parking)
28 TIMED_WAITING(sleeping)
305 WAITING (onobjectmonitor)
3 WAITING (parking)
第三步: 打开dump文件查看处于WAITING(onobjectmonitor)的线程都在干什么,是否可以停掉或者优化。

死锁

锁是一个非常有用的工具,但是他同时也会带来一些问题,那就是他有可能会会引起死锁。一旦产生死锁,就会造成系统功能不可用,是非常严重的。下面的代码会产生死锁。

    package com.swh;

public class DeadLock {
    private static  final String A = "A";
    private static  final String B = "B";


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

    public void deadLock(){
        Thread thread = new Thread(() -> {
            synchronized (A){
                try {
                    Thread.currentThread().sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B){
                    System.out.println("1");
                }

            };
        });


        Thread thread1 = new Thread(() -> {
            synchronized (B) {
                synchronized (A) {
                    System.out.println("2");
                }
            }
        });

        thread.start();
        thread1.start();
    }

}

上面的代码产生死锁的原因是 thread 拿到了临界资源A(锁A) 等待获取临界资源B (锁B)。thread1拿到了临界资源B(锁B) 等待获取临界资源A (锁A)。然后thread和thread1互相等待拿到对方的锁从而造成死锁。一旦产生死锁,业务是可感知的,因为不能继续提供服务了。可以通过dump线程查看出是哪个线程出了问题,然后解决问题。
我们说几个避免死锁的方法:

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

资源限制

什么是资源限制?

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

资源限制引发的问题。

在并发编程中,如果将某段串行的代码为了提高并发效率给成了并发编程,有可能因为资源的受限,导致多线程仍处于串行执行。这样的话不仅不会提高运行效率反而会减低,因为增加了上下文切换和资源调度的时间。

如何解决资源限制

对于硬件的资源限制,很简单加硬件资源。一种是在单机上加硬件资源。一种是可以使用集群并行执行程序。既然单机上资源有限制那么就让程序在多多机上运行,当然集群也会带来问题这个我们以后来讨论。对于软件资源限制,可以考虑使用资源池将资源服用。比如使用数据库资源连接池等。

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

如何在资源限制的情况下,让程序执行的更快呢?方法是,根绝不同的资源限制调整程序的并发度。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351