公平锁
ReentrantLock 默认是不公平的,公平锁按照顺序执行
公平锁一般没有必要,会降低并发度
import java.util.concurrent.locks.ReentrantLock;
public class FairLock {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock(true);
lock.lock();
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
// 1s 之后去争抢锁
Thread.sleep(1000);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start...");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "强行插入").start();
lock.unlock();
}
}
条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.Thread.sleep;
public class ConditionTest {
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
lock.lock();
while (!hasCigrette) {
try {
waitCigaretteQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("等到了它的烟");
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
lock.lock();
while (!hasBreakfast) {
try {
waitbreakfastQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("等到了它的早餐");
} finally {
lock.unlock();
}
}).start();
sleep(2000);
sendBreakfast();
sleep(5000);
sendCigarette();
}
private static void sendCigarette() {
lock.lock();
try {
System.out.println("送烟来了");
hasCigrette = true;
waitCigaretteQueue.signal();
} finally {
lock.unlock();
}
}
private static void sendBreakfast() {
lock.lock();
try {
System.out.println("送早餐来了");
hasBreakfast = true;
waitbreakfastQueue.signal();
} finally {
lock.unlock();
}
}
}
Java 内存模型JMM
原子性 - 保证指令不会受到线程上下文切换的影响
可见性 - 保证指令不会受 cpu 缓存的影响
有序性 - 保证指令不会受 cpu 指令并行优化的影响
可见性
main 线程对 run 变量的修改对于 t 线程不可见,导致了 t 线程无法停止
import static java.lang.Thread.sleep;
public class VisibleTest {
static volatile boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
}
System.out.println("停下来了");
});
t.start();
sleep(1000);
run = false; // 没有volatile,线程t不会如预想的停下来
System.out.println("main线程将run改为false");
}
}
因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率
1 秒之后,main 线程修改了 run 的值,并同步至主存,而 t 是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值
volatile(易变关键字)
它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存
可见性 vs 原子性
前面例子体现的实际就是可见性,它保证的是在多个线程之间,一个线程对 volatile 变量的修改对另一个线程可见, 不能保证原子性,仅用在一个写线程,多个读线程的情况
比较一下之前我们将线程安全时举的例子:两个线程一个 i++ 一个 i-- ,只能保证看到最新值,不能解决指令交错
import static java.lang.Thread.sleep;
public class VisibleTest {
static final Object lock = new Object();
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
//会一直运行,并且不释放锁,导致主线程无法运行,除非主线程先执行完,释放锁
synchronized (lock) {
while (run) {
}
}
System.out.println("停下来了");
});
t.start();
sleep(100);
synchronized (lock) {
run = false; // 没有volatile,线程t不会如预想的停下来
System.out.println("main线程将run改为false");
}
}
}
synchronized 语句块既可以保证代码块的原子性,也同时保证代码块内变量的可见性。但缺点是
synchronized 是属于重量级操作,性能相对更低
指令重排
可能的返回值有三种:1, 4, 0,actor2方法中会发生指令重排
线程2 执行 ready = true,切换到线程1,进入 if 分支,相加为 0,再切回线程2 执行 num = 2
volatile 修饰的变量,可以禁用指令重排,使volatile之前的命令都按照顺序执行