线程协作模式
同时开始
public class FireFlag {
private volatile boolean fire = false;
public synchronized void waitForFire() throws InterruptedException {
while (!fire) {
wait();
}
}
public synchronized void fire() {
this.fire = true;
notifyAll();
}
public static void main(String[] args) throws InterruptedException {
int num=10;
FireFlag fireFlag = new FireFlag();
Thread[] recers =new Thread[num];
for (int i = 0; i < num; i++) {
recers[i] = new Racer(fireFlag);
recers[i].start();
}
Thread.sleep(1_000);
fireFlag.fire();
}
static class Racer extends Thread {
FireFlag fireFlag;
public Racer(FireFlag fireFlag) {
this.fireFlag = fireFlag;
}
@Override
public void run() {
try {
this.fireFlag.waitForFire();
System.out.println("start run "+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
start run Thread-9
start run Thread-8
start run Thread-7
start run Thread-6
start run Thread-5
start run Thread-3
start run Thread-4
start run Thread-2
start run Thread-1
start run Thread-0
同时结束
同步协作工具类
public class MyLatch {
private int count;
public MyLatch(int count) {
this.count = count;
}
public synchronized void await() throws InterruptedException {
while (count > 0) {
wait();
}
}
public synchronized void countDown() {
count--;
if (count <= 0) {
notifyAll();
}
}
public static void main(String[] args) throws InterruptedException {
int workerNum=100;
MyLatch latch = new MyLatch(workerNum);
Worker[] workerNums = new Worker[workerNum];
for (int i = 0; i < workerNum; i++) {
workerNums[i] = new Worker(latch);
workerNums[i].start();
}
latch.await();
}
static class Worker extends Thread{
MyLatch myLatch;
public Worker(MyLatch myLatch) {
this.myLatch = myLatch;
}
@Override
public void run() {
try {
Thread.sleep((int)(Math.random()*1000));
this.myLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Java专门的类:CountDownLatch
集合点
public class AssemblePoint {
private int n;
public AssemblePoint(int n) {
this.n = n;
}
public synchronized void await() throws InterruptedException {
if (n > 0) {
n--;
if (n == 0) {
notifyAll();
} else {
while (n != 0) {
wait();
}
}
}
}
static class AssemblePointDemo {
static class Tourist extends Thread {
AssemblePoint ap;
public Tourist(AssemblePoint ap) {
this.ap = ap;
}
@Override
public void run() {
try {
// 先模拟各自独立运行
Thread.sleep((int)(Math.random()*1_000));
//集合
ap.await();
System.out.println(Thread.currentThread().getName()+"已到达");
// 集合之后的其他操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int num=10;
Tourist[] threads = new Tourist[num];
AssemblePoint ap = new AssemblePoint(num);
for (int i = 0; i < num; i++) {
threads[i] = new Tourist(ap);
threads[i].start();
}
}
}
}
Thread-5已到达
Thread-1已到达
Thread-3已到达
Thread-6已到达
Thread-9已到达
Thread-8已到达
Thread-0已到达
Thread-2已到达
Thread-4已到达
Thread-7已到达
Java中有一个类似的专门的同步工具类-CyclicBarrier.
线程中断
中断场景:
1-生产者消费者协作场景中,消费者就是一个无限循环,有问题时需要优雅关闭
2-用户下载任务时候,用户会随时取消
3-限时的第三方服务,时间到查不到,取消该任务
4-抢票,
打断相关方法
public boolean isInterrupted() {
return isInterrupted(false);
}
// 2-
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
// 3-还有副作用.会清空标志位
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
注意:在使用synchronized关键字获取锁的过程中不响应中断请求,这是synchronized的局限性。应该使用显式锁。
原子变量
- AtomicBoolean,在程序中表示一个标志位
- AtomicInteger
- AtomicLong,常用来在程序中生成唯一序列号
- AtomicReference 原子引用类型,以原子方式更新复杂类型。
介绍:AtomicInteger
关键方法:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
例子:
// add 1并且返回最新值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
总结:
1-synchronized是:悲观的,阻塞式的,存在上下文切换
2-CAS(CompareAndSet):乐观的,非阻塞的
实际上JUC下所有的阻塞式工具,容器,算法都是基于CAS的。
演示例子
import java.util.concurrent.atomic.AtomicInteger;
/**仅仅用于演示,实际开发中使用 ReentrantLock
* 使用AtomicInteger实现锁
* */
public class MyLock {
private AtomicInteger status = new AtomicInteger(0);
public void lock(){
while (!status.compareAndSet(0,1)){
Thread.yield();
}
}
public void unlock(){
// 0-未锁定
status.compareAndSet(1,0);
}
}
ABA问题
问题描述:当前值为A,一个线程将A修改为B,在修改为A,当前线程的CAS操作无法分辨当前值是否发生变化。
解决方式:AtomicStampedReference。在修改值的同时给一个时间戳,只有值和时间戳都相同才进行修改。
显式锁
相比synchronized显式锁,支持非阻塞方式获取锁,可响应中断,可限时。
tryLock
使用tryLock,可以避免死锁。在持有一个锁的时候获取另一个锁获取不到的时候,释放自己的锁,然后重新获取所有锁。
案例
/**
* 演示tryLock()
*/
public class Account {
private Lock lock = new ReentrantLock();
private volatile double money;
public Account(double money) {
this.money = money;
}
public double getMoney() {
return money;
}
void lock() {
lock.lock();
}
void unlock() {
lock.unlock();
}
boolean tryLock() {
return lock.tryLock();
}
public void reduce(double money) {
lock.lock();
try {
this.money -= money;
} finally {
lock.unlock();
}
}
public void add(double money) {
lock.lock();
try {
this.money += money;
} finally {
lock.unlock();
}
}
static class AccountMgr {
// 不应该这样
public static void transfer(Account from, Account to, double money) throws Exception {
from.lock;
try {
to.lock;
try {
if (from.getMoney() >= money) {
from.reduce(money);
to.add(money);
} else {
throw new Exception(from + "没有足够的钱");
}
} finally {
to.unlock();
}
} finally {
from.unlock();
}
}
// 应该这样
public static boolean tryTransfer(Account from, Account to, double money) throws Exception {
if (from.tryLock()) {
try {
if (to.tryLock()) {
try {
if (from.getMoney() >= money) {
from.reduce(money);
to.add(money);
} else {
throw new Exception(from + "没有足够的钱");
}
return true;
} finally {
to.unlock();
}
}
} finally {
from.unlock();
}
}
}
}
}
如果两个锁都能获得,且转账成功,则返回true,否则返回false。不管怎么样,结束都会释放所有的锁。
实现原理
ReentrantLock的用法比较简单,在底层是:CAS+LockSupport类的一些方法。
LockSupport类主要方法是 parkXXX
方法作用:让出CPU调度,状态变为Waiting,让别的线程搞。而Thread.yield(),只是告诉OS可以让其他线程运行,但自己依然是可运行状态。
总结:park和CAS方法一样,都是调用Unsafe类的对应方法,最终调用的就是OS的API。从java程序员角度,看是不需要关系的。
AQS
AQS:AbstractQueuedSynchronizer。并发工具。
ReentrantLock:默认获取的是非公平锁,底层通过CAS+LockSupport的peak方法
获得锁的大概流程如下图。
总结:能获得锁就立即返回。否则加入等待队列,被唤醒后检查自己是否是一个等待的线程,如果是且能获得锁,则返回,否则继续等待。这个过程弱国发生了中断,lock会记录中断标志位,但不会提前返回或者抛出异常。
ReentrantLock与synchronized区别
ReentrantLock支持非阻塞方式获取锁,可以相应中断,可以限时,更加灵活。synchronized更加简单,写的代码少,不容易出错。什么都是相对的。
synchronized代表一种声明式编程思维,程序员更多的表达一种同步声明,由java系统负责实现,程序员不知道其细节。显式锁代表一种命令式编程思维,程序员去实现所有细节。
随着新版jvm,ReentrantLock与synchronized的性能是接近的。
简单总结:能用synchronized的时候,用synchronized。
显式锁的显式条件
首先看 java.util.concurrent.locks.Lock#newCondition 这个方法
方法类型是:java.util.concurrent.locks.Condition
学习这个和Object的 wait以及notify对比学习
显式条件—await、signal。等待条件更加灵活
Object-wait,notify(对复杂条件有局限性)
切记:显式条件与显式锁配合,wait、synchronized与synchronized配合。千万不要混用,否则将抛出:IllegalMonitorStateException异常
在看生产者消费者模式
我们可以使用 synchronized+阻塞队列演示此模式。当时提到wait以及notify的一个局限性,它只能有一个条件等待队列,分析等待条件也复杂。在此模式中,有两个条件,一个与队列满有关,一个与队列空有关。
使用显式锁,可以创建多个条件等待队列。
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyBlockingQueue2<E> {
private Queue<E> queue = null;
private int limit;
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public MyBlockingQueue2(int limit) {
this.limit = limit;
queue = new ArrayDeque<>(limit);
}
public E take() throws InterruptedException {
lock.lockInterruptibly();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
E e = queue.poll();
notFull.signal();
return e;
} finally {
lock.unlock();
}
}
public void put(E e) throws InterruptedException {
lock.lockInterruptibly();
try {
while (queue.size() == limit) {
notFull.await();
}
queue.add(e);
notEmpty.signal();
} finally {
lock.unlock();
}
}
}
重申:不要将Codition中的等待和唤醒方法,与Object中混用。
优点:使用两个等待队列,更加清晰。和使用synchronized相比。
实现原理
看内部类
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject
await方法代码
public final void await() throws InterruptedException {
// 如果等待前中断标志位已经被设置,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 为当前线程创建节点,加入条件等待队列
AbstractQueuedSynchronizer.Node node = addConditionWaiter();
// 2-释放持有的锁
int savedState = fullyRelease(node);
int interruptMode = 0;
// 3- 放弃cpu,进行等待,知道被中断或者 isOnSyncQueue为true
// isOnSyncQueue为true 说明:表示该节点被其他线程从条件等待队列移到了外部锁的等待队列,等待条件满足
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 4-重新获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
// 5-处理中断,抛出异常或者设置中断标志位
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
singal唤醒方法
public final void signal() {
// 验证当前线程持有锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
AbstractQueuedSynchronizer.Node first = firstWaiter;
//调用doSignal唤醒等待队列中的第一个线程
if (first != null)
doSignal(first);
}
// doSignal方法
// 1-将节点从条件等待队列移到锁等待队列
// 2-调用 LockSupport.unpark将线程唤醒