JUC全文
读写锁
- 一个资源可以被
多个读线程访问,被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读共享
乐观锁
- 支持并发,通过版本号进行保证数据准确,以先提交版本号的为主
悲观锁
- 不支持并发操作,效率很低
表锁
- 操作一条记录时,对整个表都上锁
行锁
- 只对一条记录上锁
- 存在发生死锁的可能
读锁
- 共享锁
- 存在发生死锁的可能
两个线程对同一个资源进行读并修改,A需要等B读完才能进行修改,而B需要等A读完才能进行修改
写锁
- 独占锁
- 存在发生死锁的可能
两个线程同时对一个资源进行写操作,A先修改第一条记录再修改第二条记录,B先修改第二条记录再修改第一条记录,AB互相等待
读写锁案例
class MyCache {
private volatile Map<String, String> map = new HashMap<>();
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
//向map放
void put(String key, String val) {
try {
rwLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "正在写 - " + key);
TimeUnit.MILLISECONDS.sleep(100);
map.put(key,val);
System.out.println(Thread.currentThread().getName() + "写完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwLock.writeLock().unlock();
}
}
//取数据
String get(String key) {
rwLock.readLock().lock();
String s = null;
try {
TimeUnit.MILLISECONDS.sleep(100);
System.out.println(Thread.currentThread().getName() + "正在取 - " + key);
s = map.get(key);
System.out.println(Thread.currentThread().getName() + "取完成 - " + s);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwLock.readLock().unlock();
}
return s;
}
}
public class ReadWriteLockTest {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 5; i++) {
final int num = i;
new Thread(() -> {
myCache.put(num + "", Thread.currentThread().getName() + " - " + num);
}, "Thread" + i).start();
}
for (int i = 0; i < 5; i++) {
final int num = i;
new Thread(() -> {
myCache.get(num + "");
}, "Thread" + i + 10).start();
}
}
}
演变
- 无锁
多线程抢夺资源
synchronized或者ReentrantLock
独占式,每次只能来一个操作,资源读读不能共享
- 读写锁
ReentrantReadWriteLock
- 读读共享,同时可以让多个线程读
- 很容易造成锁饥饿,一直读会导致没有写操作。
- 读的时候,不能写,只有读完成之后,才可以写;写操作中可以有读操作;读的时候哪个线程都不能写,不能保证数据准确
- 不同线程,只能存在读锁;同一线程,可以有写锁和读锁,先释放写锁,继续持有读锁
锁降级
- 将写入锁降级为读锁
- 过程
- 获取写锁
- 获取读锁
- 释放写锁
- 读操作,释放读锁
ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
rw.writeLock().lock();
System.out.println("rw.writeLock().lock();");
rw.readLock().lock();
System.out.println("rw.readLock().lock();");
rw.readLock().unlock();
rw.writeLock().unlock();
阻塞队列
- 队列,先进先出
- 栈,后进先出
- 通过共享队列,使得数据由队列一段输入,另一端输出
- 队列为空,从队列中获取元素的操作会被阻塞
- 队列是满的,从队列中添加元素的操作会被阻塞
- 试图从空的队列获取元素的线程会被阻塞,直到其他线程往空的队列插入新的元素
- 试图向已满的队列中添加新元素的线程会被阻塞,直到其他线程从队列中移除元素或完全清空
- 阻塞,线程挂起,当可以操作时,又可以自动被唤起
基本架构
- ArrayBlockingQueue,由数组结构组成的有界阻塞队列
维护一个定长的数组,缓存队列中的数据对象
BlockingQueue<String> bq = new ArrayBlockingQueue<>(3);
//如果满了,添加,会抛异常
System.out.println(bq.add("a"));
System.out.println(bq.add("b"));
System.out.println(bq.add("c"));
System.out.println(bq.element());
//如果空了,移除,会抛异常
System.out.println(bq.remove());
//移除指定
System.out.println(bq.remove("c"));
//如果满了添加,会返回false
System.out.println(bq.offer("a"));
System.out.println(bq.offer("b"));
System.out.println(bq.offer("c"));
//查看顶部元素
System.out.println(bq.peek());
//如果空了移除,会返回false
System.out.println(bq.poll());
//等待三秒,值,时间值,单位
System.out.println(bq.offer("a", 3L, TimeUnit.SECONDS));
//放不进去会阻塞,等待能放
bq.put("a");
//队列为空,取不到,会阻塞
System.out.println(bq.take());
- LinkedBlockQueue,由链表结构组成的有界阻塞队列,但有大小为默认值
Integer.MAX_VALUE - DelayQueue,优先级队列实现的延迟无界阻塞队列
只有当指定的延迟时间到了,才能够从队列中获取到该元素
- PriorityBlockingQueue,支持优先级排序的无界阻塞队列
内部采用公平锁
- SynchronousQueue,不存储元素的阻塞队列,只能有单个元素
- LikedTransferQueue,由链表组成的无界阻塞队列
- LinkedBlockingDeque,由链表组成的双向阻塞队列