Java并发编程的艺术-笔记-第一章

Java并发编程的艺术

第一章

  • 一个例子证明并行不一定快
package com.company;

public class ConcurrencyTest {
    
    
    private static final long count = 100000;

    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--;
        }
        thread.join();//调用线程,等执行完后再继续当前线程。这个保证thread也执行完了。
        long time = System.currentTimeMillis() - start;
        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);
    }
}
/*输出:
concurrency : 2ms, b=-100000
serial: 3ms, b=-100000
*/

为甚么会这样呢,因为有上下文切换的事件
上下文切换:CPU从一个进程/线程切换到另一个进程/线程。任务从保存到再加载就是一次上下文切换。

  • 工具:
    • Lmbench3:用于测量上下文切换事件
    • vmstat:测量上下文切换的次数(CS:Content Switch,上下文切换次数)
  • 减少上下文切换:
    • 无锁并发编程: 锁竞争会引起上下文切换,用一些方法避免使用锁(如将数据的ID按Hash算法取模分段,不同线程处理不同段的数据)
    • CAS算法: CompareAndSwap算法。java.util.concurrent.*中的类使用CAS算法实现了区别于synchronized同步锁的一种乐观锁。JDK 5之前使用的是synchronized实现的独占锁(悲观锁)。
    • 使用最小线程:避免不必要的线程。(如任务少就少创建几个)
    • 使用协程:单线程中实现多任务的调度,并在单线程中维持多个任务间的切换。(Python使用yield可实现协程)
  • 实战:
    • 通过jstack这个JDK自带的堆栈分析工具进行分析。
//一个死锁
public static void deathLock() {
    
    
    Thread t1 = new Thread() {
    
    
        @Override
        public void run() {
    
    
            try {
    
    
                lock1.lock();
                TimeUnit.SECONDS.sleep(1);
                lock2.lock();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    };
    Thread t2 = new Thread() {
    
    
        @Override
        public void run() {
    
    
            try {
    
    
                lock2.lock();
                TimeUnit.SECONDS.sleep(1);
                lock1.lock();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    };
    t1.setName("mythread1");
    t2.setName("mythread2");
    t1.start();
    t2.start();
}
  • 使用jps查看所有的Java 程序的 pid
15064
7672 RemoteMavenServer36
8552 Launcher
22476 ConcurrencyTest
5468 Jps
  • jstack 22476 查看堆栈信息
打印了很多信息。
其中已经发现了死锁
Found one Java-level deadlock:
=============================
"mythread2":
  waiting for ownable synchronizer 0x00000007809645d0, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
  which is held by "mythread1"
"mythread1":
  waiting for ownable synchronizer 0x0000000780964600, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
  which is held by "mythread2"
  • 死锁发生:

    • t1拿到锁,因为异常没有释放
    • t1拿到数据库锁,释放锁时出现异常,没释放。
  • 死锁解决

    • 避免一个线程同时获得多个锁
    • 避免一个线程在锁内同时占用多个资源,尽量每个锁只占用一个
    • 定时锁,lock.tryLock(timeout)代替内部锁机制
    • 数据库锁,加锁和解锁放在一个数据库连接中。
  • 资源限制的挑战

    • 并发需要的资源量大于限制(软/硬件限制,如带宽,数据库连接数,socket连接数)。
    • 使用集群并行执行程序
    • 根据资源调整程序的并发度

猜你喜欢

转载自blog.csdn.net/o_ogou/article/details/103379128