目录
1.Lock
Lock是JUC下控制锁的接口,它要比前面用到的Synchronized更加灵活。
他有一些方法
- lock()加锁
- unlock()释放锁,它与lock()配套使用。(和Synchronized不同,Lock只能手动的释放锁)
- trylock()在不阻塞线程的情况下尝试获取锁,返回一个boolean类型
Lock接口的实现类
- ReentrantLock(重入锁)互斥锁
- ReentrantReadWriteLock(重入读写锁)共享锁
- StampedLock(读写锁)
读写锁的特点
- 读和读不互斥
- 读和写互斥
- 写和写互斥
可重入锁的特点
比如ThreadA获得了锁,但是他没有释放,它又再次想要获得锁。这就是可重入锁,如果不可重入的话在这种情况就会出现死锁的问题。
ReentrantLock源码解析
不传参的话默认是非公平锁。
用ReentrantLock锁解决原子性的线程安全问题
public class Demo1{
public static int count = 0;
public static final Lock lock = new ReentrantLock();
public static void incr() {
//获得锁(互斥锁)
lock.lock();
try {
count++;
}finally {
//释放锁
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
//创建1000个线程执行 incr方法
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
Demo1.incr();
}).start();
}
//睡3秒 确保1000个线程执行结束
Thread.sleep(3000);
System.out.println("结果" + count);
}
}
lock()方法源码分析:
private volatile int state; //互斥资源 0 在AQS抽象类中定义的
lock() -> AQS.lock()
final void lock() {
//cas这个方法是线程安全的| 只有一个线程能够进入.
if (compareAndSetState(0, 1)) //如果从0改成1 说明能抢占到
//能够进入到这个方法 ,把获得锁的线程放进去
//保存当前的线程
setExclusiveOwnerThread(Thread.currentThread());
else
//没拿到锁的线程 进入这个逻辑
acquire(1);
}
public final void acquire(int arg) {
//尝试去加锁,看锁是不是释放了,该方法也有2个实现,FairSync与 NonfairSync,
//我们暂时只看默认的NonfairSync //如果tryAcquire为false,则执行acquireQueued,
//并且 acquireQueued为true,执行selfInterrupt
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- ! tryAcquire(arg) 尝试获取锁
- addWaiter 将未获得锁的线程加入到AQS同步队列
- acquireQueued(); 去抢占锁或者阻塞.
tryAcquire->nonfairTryAcquire 非公平的实现方式
final boolean nonfairTryAcquire(int acquires) {
//获得当前的线程
final Thread current = Thread.currentThread();
//获取AbstractQueuedSynchronizer的值,因为线程1把他设为了 1,所以c为1
int c = getState();
//c为0,是线程1释放了锁的场景,但是现在没有,不满足
if (c == 0) { //无锁
if (compareAndSetState(0, acquires)) { //cas
//设置当前执行代码块的线程为线程2
setExclusiveOwnerThread(current);
//返回ture,代表线程2拿到了锁,去正常执行业务代码
return true;
}
}
//判断?重入
else if (current == getExclusiveOwnerThread()) {
//将state 改成state +1 这个时候,state不止是一个状态,而 是代表加锁了多少次(重入次数)
int nextc = c + acquires; //增加state的值
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc); //并不需要通过cas 因为可重入
return true;
}
///如果抢占不到锁,并且当前线程不是占有锁线程,返回false
return false;
}
addWaiter 会把未获得锁的线程顺序的加入双向链表中(AQS的同步队列)
//参数mode 为null(在lock没有用处 condition中有用)
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
//new node对象,对象为AQS中的一个数据结构,里面存有线程信息,
//以及前 后节点数据、首尾节点等信息
// Try the fast path of enq; backup to full enq on failure
//tail node里面定义尾结点,默认为null
Node pred = tail;
//所以,thread2进来的时候,pred为null,不满足下面条件
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//进入enq方法
enq(node);
//返回node节点
return node;
}
enq方法
//传入的是new的那个node
private Node enq(final Node node) {
//自旋
for (;;) {
//tail默认为null
Node t = tail;
//第一次满足条件
if (t == null) { // Must initialize
//cas去设置head节点
if (compareAndSetHead(new Node()))
//设置成功,将tail设置为head节点,继续自旋
tail = head;
} else {
//第二次自旋,当t!=null时进入
//node的前节点指向t
node.prev = t;
//cas将tail节点设置为node
if (compareAndSetTail(t, node)) {
//设置t的next为node
t.next = node;
//跳出循环,返回t
return t;
}
}
}
}
//node为当前线程的node节点
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取node节点的前一个节点
final Node p = node.predecessor();
//如果p==head 如果是thread2,那么满足,但是thread2 tryAcquire失败,
//因为thread1占有锁;所以不满足
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//进入shouldParkAfterFailedAcquire与 parkAndCheckInterrupt
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//得到节点的状态,默认为0 第二 次进入为-1
int ws = pred.waitStatus;
//waitStatus!=-1不满足 第二次 满足条件,返回true
if (ws == Node.SIGNAL)
return true;
if (ws > 0) { //不满足
//该逻辑是当我前一个节点的线程状态取消时,将前后链表的关系取 消
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { //进入else
//修改当前节点的前一个节点的状态为-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//返回false,进入外面的自旋,
return false;
}
parkAndCheckInterrupt方法
private final boolean parkAndCheckInterrupt() {
//如果没有拿到锁,线程park
LockSupport.park(this);
//获取中断状态并且复位
return Thread.interrupted();
}
unlock()方法源码分析
拿到锁的线程执行unlock方法释放锁
public void unlock() {
sync.release(1);
}
release()方法
public final boolean release(int arg) {
//调用tryRelease方法 尝试释放锁
if (tryRelease(arg)) {
//如果锁释放成功
Node h = head;
//得到head节点,如果head节点!=null 并且状态 !=0 满足条件
if (h != null && h.waitStatus != 0)
//执行unparkSuccessor
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease()方法 更改state状态
protected final boolean tryRelease(int releases) {
//加入thread1只重入了一次。c-1=0
int c = getState() - releases;
//判断加锁的线程是不是当前需要释放的线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果thread1只加锁一次
if (c == 0) {
free = true;
//将当前拿锁线程设置为空
setExclusiveOwnerThread(null);
}
//将state设置为0
setState(c);
return free;
}
unparkSuccessor方法
//node 为head节点
private void unparkSuccessor(Node node) {
//得到head的节点状态
int ws = node.waitStatus;
if (ws < 0) //现在的状态为-1 满足条件
//将头节点改成 0
compareAndSetWaitStatus(node, ws, 0);
//得到head节点的下一个节点,我们的场景为thread2的node
Node s = node.next;
//thread2node的状态为-1,正常状态
if (s == null || s.waitStatus > 0) {
s = null;
//如果需要释放锁的那个线程是取消状态,从后往前找到最前面的那 一个node
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//满足条件
if (s != null)
//唤醒阻塞的线程(头节点的下一个节点)
LockSupport.unpark(s.thread);
}
head的下一个节点被唤醒
会从线程被阻塞的地方唤醒 也就是parkAndCheckInterrupt方法
private final boolean parkAndCheckInterrupt() {
//如果没有拿到锁,线程park
LockSupport.park(this);
//被释放锁唤醒,如果外面中断过范围false,否则返回true,并复位
return Thread.interrupted();
}
进入acquireQueued方法进行自旋
//假设被唤醒的是thread2
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取thread2 node的前节点
final Node p = node.predecessor();
//thread2的前节点是head,并且这个时候thread1已经释放了锁,能拿到锁
if (p == head && tryAcquire(arg)) {
setHead(node);
//这个时候之前的node节点还存在强引用,将next关联去除,
//方便回收之前的head节点
p.next = null; // help GC
failed = false;
//返回
return interrupted;
}
//进入shouldParkAfterFailedAcquire与 parkAndCheckInterrupt
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
图解
公平锁和非公平锁的区别
非公平锁就是允许插队,比如线程1刚执行unlock方法释放了锁,这时AQS同步队列中有线程2正再阻塞,这时他刚要被唤醒,此时不在队列的线程3直接来抢占锁,并且拿到了锁,这就是非公平的体现。
公平锁是不允许插队的。
非公平锁的实现思路就是,先抢占锁,抢不到我再进行排队。
公平锁的实现思路就是,先判断AQS同步队列中是否有线程排队,如果有我就去排在后边,如果没有我在尝试抢占锁。
什么是AQS
AQS是juc包下的一个工具类,全称是AbstractQueuedSynchronizer 抽象队列同步器。我们并发编程中的很多类的功能底层都是基于AQS来实现。
比如ReentrantLock 、CountDownLatch 、condition等等。
首先数据结构。既然是实现并发的核心类,那么AQS中维护了一个state字段,代表锁的抢占情况。并提供对state的cas操作。以及提供加锁的模板方法,比如tryAcquire,自己可以去重现实现相关逻辑。
同时,抢不到的线程需要排队并且等待,所以AQS中有个线程等待队列。它里面最主要的是有一个双向列表。 节点的数据结构是node, node存有线程的信息,以及node的状态。同时提供对双向列表的相关操作方法。
如果线程抢占不到锁,就会进入AQS中的等待队列,并且park。同时提供了释放锁都相关方法,释放锁会唤醒相关线程。进行锁抢占操作
Synchronized和Lock的区别
1.synchronized底层jvm层面实现 ReentrantLock 为java的juc类
2.synchronized被动释放锁 lock必须手动释放
3.synchronized不能响应中断,lock可以响应中断
4.synchronized可重入,只能非公平 ; lock可重入,可公平与非公平
5.Lock提高读写能力,在1.6之前性能比synchronized高,但是1.6之后,synchronized加入了锁升级也使用了CAS思想,是的他们间的性能差不多了
2.Condition( 线程通信)
- Condition await()阻塞线程并释放锁(把线程放发哦condition等待队列)
- Condition signal() signalAll() 唤醒阻塞状态的线程(把condition等待队列的线程唤醒到AQS队列的尾部)
实现生产者消费者案例
Producer
public class Producer implements Runnable{
private Queue<String> bags;
private int size;
private final Lock lock;
private Condition condition;
public Producer(Queue bags, int size, Lock lock, Condition condition) {
this.bags = bags;
this.size =size;
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
int i = 0;
while (true) {
i++;
lock.lock();
try {
while (bags.size() == size) {
System.out.println("bags以满");
try {
//阻塞 释放锁
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
//一秒生产一个
Thread.sleep(1000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
bags.add("bags-"+i);
System.out.println("生产者-生产:bags-" + i);
//唤醒阻塞状态的消费者
condition.signal();
} finally {
lock.unlock();
}
}
}
}
Consumer
public class Consumer implements Runnable{
private Queue<String> bags;
private int size;
private final Lock lock;
private Condition condition;
public Consumer(Queue bags, int size, Lock lock, Condition condition) {
this.bags = bags;
this.size =size;
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
int i = 0;
while (true) {
i++;
lock.lock();
try {
while (bags.isEmpty()) {
System.out.println("bags以空");
//阻塞 释放锁
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
//一秒消费一个
Thread.sleep(1000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
String bag = bags.remove();
System.out.println("消费者-消费:bags-" + i);
//唤醒阻塞的生产者
condition.signal();
} finally {
lock.unlock();
}
}
}
}
Test
public class AwaitSignalDemo {
public static void main(String[] args) {
Queue<String> bags = new LinkedList<>();
int size = 5;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread p = new Thread(new Producer(bags,size,lock,condition));
Thread c = new Thread(new Consumer(bags,size,lock,condition));
p.start();
c.start();
}
}
简单图解
3.BlockQueue阻塞队列
插入操作
add(e) :添加元素到队列中,如果队列满了,继续插入元素会报错,IllegalStateException。
offer(e) : 添加元素到队列,同时会返回元素是否插入成功的状态,如果成功则返回true
put(e) :当阻塞队列满了以后,生产者继续通过put添加元素,队列会一直阻塞生产者线程,知道队列可用
offer(e,time,unit) :当阻塞队列满了以后继续添加元素,生产者线程会被阻塞指定时间,如果超时,则线程直接退出
移除操作
remove():当队列为空时,调用remove会返回false,如果元素移除成功,则返回true
poll(): 当队列中存在元素,则从队列中取出一个元素,如果队列为空,则直接返回null
take():基于阻塞的方式获取队列中的元素,如果队列为空,则take方法会一直阻塞,直到队列中有新的数据可以消费
poll(time,unit):带超时机制的获取数据,如果队列为空,则会等待指定的时间再去获取元素返回
4.CountDownLatch
计数器
public class Demo3 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行业务");
}
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 5; i++) {
new Thread(()-> {
try {
//当countDownLatch 为0时会被唤醒
//也可以被interrupt唤醒
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行业务");
}).start();
}
System.out.println("主线程执行");
countDownLatch.countDown(); //countDownLatch 会减一
}
}
CountDownLatch countDownLatch = new CountDownLatch(1); //相当于把AQS里的state赋值 在lock中state作为锁的标志 在countDownLactch中state作为计数器
countDownLatch.await(); //如果countDownLacth != 0 就会把线程放到AQS同步队列,并park; 当countDownLacth ==0 会被 唤醒 还可以被interrupt唤醒
countDownLatch.countDown(); //会让state-1
countDownLatuch在并发查询的时候,我们为了增加查询效率,一般是会加入缓存。并给缓存添加过期时间。假如:并发情况下,出现,缓存穿透,也就是缓存没了,需要查库的时候。怎么解决
可以使用countDownLatuch cdl=new CountDownLatuch(1);只允许一个线程去获取缓存,如果缓存不存在,重新从库里拿出来加入缓存。然后放行所有线程通过执行下层逻辑。
可以用一个监听器,监听器那边,监听器那边down,主业务await啥的。
5.Semaphore(信号灯)
实现个小Demo
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5);//指定令牌数
for (int i = 0; i < 10; i++) {
//启动10个Car线程
new Car(semaphore, i).start();
}
}
static class Car extends Thread{
Semaphore semaphore;
int num;
public Car(Semaphore semaphore, int num) {
this.semaphore = semaphore;
this.num = num;
}
@Override
public void run() {
try {
semaphore.acquire();//获取令牌(没拿到令牌,会阻塞,拿到了就可以往下执行)
System.out.println("第"+num+"线程,占用令牌");
Thread.sleep(2000);
System.out.println("第"+num+"线程,释放令牌");
semaphore.release(); //释放令牌
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
6.Atomic原子类
解决原子性问题
public class Demo2 {
//使用原子类
public static AtomicInteger count = new AtomicInteger(0);
public static void incr() {
//count++
count.getAndIncrement();
}
public static void main(String[] args) throws InterruptedException {
//创建1000个线程执行 incr方法
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
Demo2.incr();
}).start();
}
//睡3秒 确保1000个线程执行结束
Thread.sleep(1000);
System.out.println("结果" + count.get());
}
}
多次执行,线程安全;