JAVA并发编程-简述

本系列文章主要参考书籍和网上的资料来对JAVA并载编程进行自我总结,方便更好的学习和理解。

相关代码 https://gitee.com/dogman/concurrent-example.git

 

并发编程的最终目的是为了让程序运行的更快,但是线程开的多,并不代表着程序能够更快的运行,多线程在并发编程时受到各类并发所引起的各种问题和瓶颈,例如死锁问题,上下文优化问题,线程同步操作等限制,导致程序不能很好的运行,本文主要会对此类问题进行一些描述及解决方案输出。

本节内容

  1. 多线程比单线程慢的例子
  2. 死锁现象描述
  3. 资源限制的挑战

 

线程开的多一定就快吗?

学过操作系统的同学,都会知道,CPU在操作系统层面执行程序的时间,是以时间片为单位来给不同的任务分配运行,CPU通过不停的切换任务,而这个时间片又特别短,一般为几十MS。所以我们感觉是多个任务在同时运行。而多个任务在进行切换时,是需要保存该任务的上下文信息,也就是每一次切换,都会导致保存上下任务的上下文信息存储,和切换到的线程的上下文读取。所以开的线程多,不一定会快。

下面就是一个反例,

该程序测试了并发执行和串行执行同一个操作,我们可以通过

package com.ssj.java.demo.concourrent;

import org.junit.Test;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author shuai.zhang  on 2018/10/16
 * @description
 */
public class TestConcourrentAndSerial {

    static int repeatCount = 50000;
    @Test
    public void testConcourrentAndSerial() throws InterruptedException {
        concourrent();
        serial();
    }

    private void serial() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < repeatCount; i++) {
            doSomething();
        }
        long overTime = System.currentTimeMillis();
        System.out.println(String.format("serial cost time %.3fs", (overTime - startTime)/1000.0));
    }

    private void doSomething() {
        Long a = System.currentTimeMillis();
        a = a % 100;
    }

    /*多线程操作*/
    private void concourrent() throws InterruptedException {
        long startTime = System.currentTimeMillis();
        ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(100);

        for (int i = 0; i < repeatCount; i++) {
            executorService.execute(() -> {
                doSomething();
            });
        }

        while(executorService.getCompletedTaskCount() != repeatCount) {
            Thread.sleep(1);
        }

        long overTime = System.currentTimeMillis();
        System.out.println(String.format("concourrent cost time %.3fs", (overTime - startTime)/1000.0));
    }

}

测试结果如下:

可以看到当特别简单的操作时,其实串行操作要比并发操作还要快。

如何查看上下文切换次数呢,可以通过操作系统LINUX(提供的命令)vmstat进行查询,其中cs(content switch)代表上下文切换,可以当作我们评价程序上下文切换的参考指标

 

如何减少上下文切换的次数?

1.尽量少使用锁

2.使用CAS算法(compare and swap)

3.尽量减少并发线程数(可参考当前CPU数)

4.使用协程

 

死锁现象

死锁现象,在并发编程中,是一种很常见的问题,会出来在有多个锁出现的场景,

package com.ssj.java.demo.concourrent;

import org.junit.Test;

/**
 * @author shuai.zhang  on 2018/10/16
 * @description
 */
public class TestDeadLock {

    final static Integer lockA = new Integer(1);
    final static Integer lockB = new Integer(1);
    @Test
    public void testDeadLock() throws InterruptedException {
        Thread testOne = new Thread(() -> {
            synchronized (lockA) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
                synchronized (lockB) {
                    System.out.println("testOne in content");
                }
            }
        });

        Thread testTwo = new Thread(() -> {
            synchronized (lockB) {
                synchronized (lockA) {
                    System.out.println("testTwo in content");
                }
            }
        });

        testOne.start();
        testTwo.start();

        testOne.join();
        testTwo.join();
    }
}

执行后,可见程序进行到不可完成的阻塞状态,通过jstack 链接到该程序,我们可以看到该程序的两个线程都在等待lock操作

 

资源限制对并发的限制

在资源有限的情况下,并发并不能够突破已存在的资源的限制,如CPU计算力,硬件读写能力,网络吞吐能力,数据库操作能力等。所以我们要结合实际的硬件资源,来对线程数等进行合理的调整,已达到在一定资源限制的情况下,合理调整线程数,来达到程序能力的最大话。

 

参考书籍

JAVA并发编程艺术

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值