java并发编程的学习 ——java并发编程具体在哪些领域和公司用到比较多? web开发要用到吗?

114 阅读3分钟

使用并发的原因

多核的CPU的背景下,催生了并发编程的趋势,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。
在特殊的业务场景下先天的就适合于并发编程。 比如在图像处理领域,一张1024X768像素的图片,包含达到78万6千多个像素。即时将所有的像素遍历一边都需要很长的时间, 面对如此复杂的计算量就需要充分利用多核的计算的能力。又比如当我们在网上购物时,为了提升响应速度,需要拆分,减库存, 生成订单等等这些操作,就可以进行拆分利用多线程的技术完成。 面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分。
Java线程的生命周期

  1. java线程的生命周期如下图所属在这里插入图片描述
    1、上下文切换
    多线程的支持:CPU通过给每个线程分配CPU时间片来实现这个机制。

上下文切换:CPU通过分配时间片算法循环执行任务时,任务切换前会保存前一个任务的状态,以便切回任务,任务从保存到再次加载的过程就是一次上下文切换。上下文切换会影响多线程的执行速度。

1.1多线程一定快吗?

代码见part01中ConcurrencyTest类。

package io.ilss.concurrency.part01;

/**
 * className ConcurrencyTest
 * description ConcurrencyTest
 *
 * @author feng
 * @version 1.0
 * @date 2019-01-21 12:47
 */
public class ConcurrencyTest {
    private static final long count = 100001L;

    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() {
            public void run() {
                long a = 0;
                for (long i = 0; i < count; i++) {
                    a += 5L;
                }
            }
        });
        thread.start();
        long 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();
        long a = 0;
        for (long i = 0; i < count; i++) {
            a += 5;
        }
        long 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);
    }
}

通过更改count从1万到1亿可看成,执行count越低,串行执行效率更高。这是因为线程有创建和上下文切换的开销。但是到了一定的数量过后,多线程的优势就会体现出来。

1.2

可以用Lmbench3测量上下文切换的时间 (Lmbench3是一个性能分析工具)

1.3 如何减少上下文切换

减少上下文切换的方法:无锁并发编程、CAS算法、使用最少线程、使用协程

无锁并发编程:即用一些方法来避免使用锁。
CAS算法:Atomic包使用CAS算法更新数据
只用最少线程:避免创建不需要的线程
协程:在单线程中实现多任务调度,并在单线程中维持多个任务间的切换

1.4死锁

代码见part01中的DeadLockDemo类

package io.ilss.concurrency.part01;

/**
 * className DeadLockDemo
 * description DeadLockDemo
 *
 * @author feng
 * @version 1.0
 * @date 2019-01-21 16:28
 */
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(() -> {
            synchronized (A) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B) {
                    System.out.println("1");
                }
            }
        });
        Thread t2 = new Thread(() ->{
            synchronized (B) {
                synchronized (A) {
                    System.out.println("2");
                }
            }
        });
        t1.start();
        t2.start();

    }
}

t1(Thread-0)拿到A锁过后sleep2s,t2("Thread-1)同时进入B锁代码块,由于t1还在执行A锁的代码块阻塞进程,等t1线程2s时间到了想进入B的代码块时,由于t2还在执行B锁的代码块,就行形成了拿了A的t1要B,拿了B的t2要A,形成死锁。如下信息所示

Java stack information for the threads listed above:
===================================================
"Thread-1":
    at io.ilss.concurrency.part01.DeadLockDemo.lambda$deadLock$1(DeadLockDemo.java:36)
    - waiting to lock <0x000000076ac26378> (a java.lang.String)
    - locked <0x000000076ac263a8> (a java.lang.String)
    at io.ilss.concurrency.part01.DeadLockDemo$$Lambda$2/2129789493.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)
"Thread-0":
    at io.ilss.concurrency.part01.DeadLockDemo.lambda$deadLock$0(DeadLockDemo.java:29)
    - waiting to lock <0x000000076ac263a8> (a java.lang.String)
    - locked <0x000000076ac26378> (a java.lang.String)
    at io.ilss.concurrency.part01.DeadLockDemo$$Lambda$1/1607521710.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

注:先使用jps找到运行所在的pid,然后用jstack 查看如上信息
避免死锁的几个常见的方法:

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

并发编程时需要考虑:硬件资源如带宽的上传/下载速度、硬盘的读写速度和CPU的处理速度。软件资源如数据库连接数、socket连接数等
资源限制引发的问题:多线程程序由于资源限制,仍然串行执行,会因为上下文切换和资源调度而降低效率
解决资源限制的问题:硬件上可以使用集群,不同的机器处理不同的数据;软件上可以考虑使用资源池将资源复用,如:数据库连接池、socket连接复用、调用webservice接口获取数据时,只建立一个连接。
在资源限制情况下进行并发线程:根据不同的资源限制调整程序的并发度。