第二章 线程与锁
2.1 简单粗暴
线程与锁模型是对底层硬件运行过程的形式化,该模型非常简单,但是使得程序容易出错并且难以维护。
2.2 第一天:互斥和内存模型
互斥--用锁保证某一时间仅有一个线程可以访问数据。带来竞态条件和死锁等问题。
竞态条件:代码行为取决于各操作的顺序。
看下面例子,猜测下输出内容:
public class Puzzle {
static boolean answerReady = false;
static int answer = 0;
static Thread t1 = new Thread(() -> {
answer = 42;
answerReady = true;
});
static Thread t2 = new Thread(() -> {
if (answerReady) {
System.out.println("the meaning of life is:" + answer);
} else {
System.out.println("I don't know the answer");
}
});
public static void main(String[] args) throws InterruptedException {
t1.start();
t2.start();
t1.join();
t2.join();
}
}
- The meaning of life is 42
- I don't know the answer
- The meaning of life is 0
以上三种都可能,因为可能颠倒顺序:
- 编译器的静态优化可以打乱代码的执行顺序
- JVM的动态优化也会打乱代码的执行顺序
- 硬件可以通过乱序执行来优化其性能
2.3 第二天 超越内置锁
内置锁虽然方便但是限制很多:
- 一个线程因为等待内置锁而进入阻塞之后,就无法中断该线程了
- 尝试获取内置锁时,无法设置超时
- 获得内置锁,必须使用synchronized块
可中断锁:ReentrantLock.lockInterruptibly()
超时:ReentrantLock.tryLock(100, TimeUnit)
交替锁:链表中插入元素,不锁整个链表
public class ConcurrentSortList {
private final Node head;
private final Node tail;
public ConcurrentSortList() {
this.head = new Node();
this.tail = new Node();
head.next = tail;
tail.prev = head;
}
public void insert(int value) {
Node current = head;
current.lock.lock();
Node next = current.next;
try {
while (true) {
next.lock.lock();
try {
if (next == tail || next.value > value) {
Node node = new Node(value, current, next);
next.prev = node;
current.next = node;
return;
}
} finally {
current.lock.unlock();
}
current = next;
next = current.next;
}
} finally {
next.lock.unlock();
}
}
private class Node {
int value;
Node prev;
Node next;
ReentrantLock lock = new ReentrantLock();
Node() {
}
Node(int value, Node prev, Node next) {
this.value = value;
this.prev = prev;
this.next = next;
}
}
}
条件变量:等待某个事件发生
原子变量:无锁非阻塞算法的基础,这种算法可以不用锁和阻塞来达到同步的目的。
volatile变量:是一种低级形式的同步,并不能解决原子性的问题,随着JVM被不断优化,其提供了一些低开销的锁,volatile变量的适用场景也越来越少。
2.4 第三天:站在巨人的肩膀上
线程池
写时复制:保护性复制策略,在列表被修改时复制,已经投入使用的迭代器会使用当时的旧副本。
网友评论