悲观锁:不支持并发,效率低,但是可以解决所有并发安全问题
乐观锁:支持并发读,维护一个版本号,写的时候比较版本号进行控制,先提交的版本号的线程可以进行写。
表锁:只操作一条记录的时候,对整张表上锁
行锁:只对一条记录上锁,行锁会发生死锁。
读锁:共享锁,发生死锁
写锁:独占锁,发生死锁
读锁发生死锁案例:
写锁发生死锁案例:
现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那 么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
针对这种场景,JAVA 的并发包提供了读写锁 ReentrantReadWriteLock, 它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁
//资源类
class MyCache {
//创建map集合
private volatile Map<String,Object> map = new HashMap<>();
//创建读写锁对象
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
//放数据
public void put(String key,Object value) {
//添加写锁
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);
//暂停一会
TimeUnit.MICROSECONDS.sleep(300);
//放数据
map.put(key,value);
System.out.println(Thread.currentThread().getName()+" 写完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放写锁
rwLock.writeLock().unlock();
}
}
//取数据
public Object get(String key) {
//添加读锁
rwLock.readLock().lock();
Object result = null;
try {
System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);
//暂停一会
TimeUnit.MICROSECONDS.sleep(300);
result = map.get(key);
System.out.println(Thread.currentThread().getName()+" 取完了"+key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放读锁
rwLock.readLock().unlock();
}
return result;
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) throws InterruptedException {
MyCache myCache = new MyCache();
//创建线程放数据
for (int i = 1; i <=5; i++) {
final int num = i;
new Thread(()->{
myCache.put(num+"",num+"");
},String.valueOf(i)).start();
}
TimeUnit.MICROSECONDS.sleep(300);
//创建线程取数据
for (int i = 1; i <=5; i++) {
final int num = i;
new Thread(()->{
myCache.get(num+"");
},String.valueOf(i)).start();
}
}
}
注意:如果不进行加锁的操作,可能会存在没写完就开始读,然后读出null的情况
读写锁:一个资源可以被一个或多个读线程访问,或者被一个写线程访问。读写互斥,读读共享
代码演示:
//锁降级
//1 获取写锁
writeLock.lock();
System.out.println("-- writelock");
//2 获取读锁
readLock.lock();
System.out.println("---read");
//3 释放写锁
writeLock.unlock();
//4 释放读锁
readLock.unlock();
// 输出结果:
-- writelock
---read
因为读写锁是可重入锁,所以,加完写锁可以加读锁。
但是反过来就不行了
//2 获取读锁
readLock.lock();
System.out.println("---read");
//1 获取写锁
writeLock.lock();
System.out.println("-- writelock");
//3 释放写锁
writeLock.unlock();
//4 释放读锁
readLock.unlock();
// 输出结果:
---read
卡住了
原因: 当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程“升级”为写锁。而对于获得写锁的线程,它一定独占了读写 锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释 放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。
一句话总结:使用优先级队列实现的延迟无界阻塞队列。
一句话总结:由链表组成的无界阻塞队列。
代码演示:
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
//创建阻塞队列
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
//第一组
System.out.println(blockingQueue.add("a")); // 打印true
System.out.println(blockingQueue.add("b")); // 打印true
System.out.println(blockingQueue.add("c")); // 打印true
System.out.println(blockingQueue.element()); // 打印a
System.out.println(blockingQueue.add("w")); // 抛出异常 Queue full
System.out.println(blockingQueue.remove()); // 打印a
System.out.println(blockingQueue.remove()); // 打印b
System.out.println(blockingQueue.remove()); // 打印c
System.out.println(blockingQueue.remove()); // 抛出异常 NoSuchElementException
//第二组
System.out.println(blockingQueue.offer("a")); // 打印 true
System.out.println(blockingQueue.offer("b")); // 打印 true
System.out.println(blockingQueue.offer("c")); // 打印 true
System.out.println(blockingQueue.offer("www")); // 打印 false
System.out.println(blockingQueue.poll()); // 打印a
System.out.println(blockingQueue.poll()); // 打印b
System.out.println(blockingQueue.poll()); // 打印c
System.out.println(blockingQueue.poll()); // 打印 null
//第三组
blockingQueue.put("a"); //
blockingQueue.put("b"); //
blockingQueue.put("c"); //
blockingQueue.put("w"); // 陷入阻塞
System.out.println(blockingQueue.take()); // 打印a
System.out.println(blockingQueue.take()); // 打印b
System.out.println(blockingQueue.take()); // 打印c
System.out.println(blockingQueue.take()); // 陷入阻塞
//第四组
System.out.println(blockingQueue.offer("a")); // 打印true
System.out.println(blockingQueue.offer("b")); // 打印true
System.out.println(blockingQueue.offer("c")); // 打印true
System.out.println(blockingQueue.offer("w",3L, TimeUnit.SECONDS)); // 等待了3秒,打印false
}
}
1. 在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起
2. 为什么需要 BlockingQueue? 在 concurrent 包发布以前,在多线程环境下, 我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全, 而这会给我们的程序带来不小的复杂度。使用后我们不需要关心什么时候需要 阻塞线程,什么时候需要唤醒线程,因为这一切 BlockingQueue 都给你一手 包办了
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销, 进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理 者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代 价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
例子: 10 年前单核 CPU 电脑,假的多线程,像马戏团小丑玩多个球,CPU 需 要来回切换。 现在是多核电脑,多个线程各自跑在独立的 CPU 上,不用切换 效率高。
线程池的优势: 线程池做的工作只要是控制运行的线程数量,处理过程中将任 务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量, 超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
它的主要特点为:
// 扩容线程池
ExecutorService threadPool3 = Executors.newCachedThreadPool();
try {
for(int i=1;i<=20;i++){
final int num = i;
threadPool3.execute(()->{
System.out.println(Thread.currentThread().getName()+" 正在办理业务"+num);
});
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
threadPool1.shutdown();
}
// 输出:
pool-3-thread-1 正在办理业务1
pool-3-thread-3 正在办理业务3
pool-3-thread-2 正在办理业务2
pool-3-thread-4 正在办理业务4
pool-3-thread-5 正在办理业务5
pool-3-thread-2 正在办理业务6
pool-3-thread-1 正在办理业务10
pool-3-thread-4 正在办理业务8
pool-3-thread-3 正在办理业务9
pool-3-thread-1 正在办理业务13
pool-3-thread-5 正在办理业务7
pool-3-thread-2 正在办理业务14
pool-3-thread-4 正在办理业务12
pool-3-thread-1 正在办理业务17
pool-3-thread-3 正在办理业务19
pool-3-thread-5 正在办理业务16
pool-3-thread-2 正在办理业务18
pool-3-thread-6 正在办理业务11
pool-3-thread-8 正在办理业务20
pool-3-thread-7 正在办理业务15
// 一池5线程
ExecutorService threadPool1 = Executors.newFixedThreadPool(5);
try {
for(int i=1;i<=10;i++){
final int num = i;
threadPool1.execute(()->{
System.out.println(Thread.currentThread().getName()+" 正在办理业务"+num);
});
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
threadPool1.shutdown();
}
// 输出:
pool-1-thread-1 正在办理业务1
pool-1-thread-3 正在办理业务3
pool-1-thread-2 正在办理业务2
pool-1-thread-4 正在办理业务4
pool-1-thread-5 正在办理业务5
pool-1-thread-4 正在办理业务7
pool-1-thread-5 正在办理业务8
pool-1-thread-4 正在办理业务9
pool-1-thread-5 正在办理业务10
pool-1-thread-3 正在办理业务6
// 一池一线程
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
try {
for(int i=1;i<=10;i++){
final int num = i;
threadPool2.execute(()->{
System.out.println(Thread.currentThread().getName()+" 正在办理业务"+num);
});
}
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
threadPool1.shutdown();
}
// 输出:
pool-2-thread-1 正在办理业务1
pool-2-thread-1 正在办理业务2
pool-2-thread-1 正在办理业务3
pool-2-thread-1 正在办理业务4
pool-2-thread-1 正在办理业务5
pool-2-thread-1 正在办理业务6
pool-2-thread-1 正在办理业务7
pool-2-thread-1 正在办理业务8
pool-2-thread-1 正在办理业务9
pool-2-thread-1 正在办理业务10
11.2 中介绍的三个线程池底层实现都使用了ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
项目中创建多线程时,使用常见的三种线程池创建方式,单一、可变、定长都 有一定问题,原因是 FixedThreadPool 和 SingleThreadExecutor 底层都是用 LinkedBlockingQueue 实现的,这个队列最大长度为 Integer.MAX_VALUE, 容易导致 OOM。所以实际生产一般自己通过 ThreadPoolExecutor 的 7 个参 数,自定义线程池
创建线程池推荐适用ThreadPoolExecutor及其7个参数手动创建
为什么不允许适用不允许Executors.的方式手动创建线程池,如下图
代码示例:
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 2L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
//10个顾客请求
try {
for (int i = 1; i <=10; i++) {
//执行
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" 办理业务");
});
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭
threadPool.shutdown();
}