Java并发编程的艺术(读书笔记)

第一章 并发编程的挑战(0312)

  • 即使是单核处理器也支持多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制,CPU不断地切换线程执行

  • 上下文切换: 任务从上一个保存到再次加载这个任务的过程就是一次上下文切换

  • 并发不一定比串行快,因为线程有创建和上下文切换的开销

  • Lmbench3可以测量上下文切换的时长

    vmstat可以测量上下文切换的次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class ConcurrentTest {
//实测只有上亿次 并发才会比串行快
private static final Long count=100000000L;

public static void main(String[] args) throws InterruptedException {
concurrency();
serial();
}

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

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

Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (int i = 0; i < count; i++) {
a += 5;
}
}
});

thread.start();
int b = 0;
for (int i = 0; i < count; i++) {
b--;
}
thread.join();
long time=System.currentTimeMillis()-start;
System.out.println("concurrency:"+time+"ms,b="+b);
}

}

如何减少上下文切换

  • 无锁并发编程

    多线程竞争锁,引起上下文切换. 所以多线程处理数据时候可以使用方法避免使用锁

    eg: 将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据

  • CAS算法:

    Java的Atomic包使用CAS算法来更新数据,而不需要加锁

  • 使用最少线程

    避免创建不需要的线程

  • 协程

    在单线程里实现多任务的调度,维持多个任务的切换

死锁

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

资源限制

程序的执行速度受限于计算机硬件资源或软件资源

解决:

  • 硬件资源限制: 集群并行执行
  • 软件资源限制: 资源复用 eg:线程池将数据库和Socket连接复用

第二章 并发机制的底层实现原理

volatile的应用

volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的可见性.可见性的意思就是当一个线程修改一个共享变量时候,另外一个线程能读到这个修改的值.

如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为他不会引起上下文的切换和调度