并发编程—《Java并发编程的艺术》学习笔记 第一章 并发编程的挑战

Java并发编程的艺术所有笔记https://blog.csdn.net/ilo114/article/category/8633278
Github地址:https://github.com/ilssio/java-concurrency-programming-ilss

1.1上下文切换

​ 多线程的支持:CPU通过给每个线程分配CPU时间片来实现这个机制。

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

1.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.1.2

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

1.1.3 如何减少上下文切换

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

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

1.1.4 减少上下文切换实战

减少WAITING线程,系统上下文切换的次数就会少。线程从WAITING到RUNNABLE都会进行一次上下文切换。

1.2 死锁

代码见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接口获取数据时,只建立一个连接。
  • 在资源限制情况下进行并发线程:根据不同的资源限制调整程序的并发度。
发布了34 篇原创文章 · 获赞 7 · 访问量 8181

猜你喜欢

转载自blog.csdn.net/ilo114/article/details/86598642