文章目录
wait和sleep的区别
- wait是Object的方法,sleep是Thread的方法
- wait会释放锁,sleep不会
- wait必须在同步代码块中使用,sleep没有要求
Callable和Runnable区别
- Callable可以有返回值可以抛出异常,Runnable则不行
- 一个run方法一个call方法
FutureTask实现了Runnable接口,并且FutureTask的构造函数有public FutureTask(Callable<V> callable)
和public FutureTask(Runnable runnable, V result)
,所以只要我们拥有一个实现了Callable接口的类,并将其传入FutureTask即可,我们便拥有了实现了Runnable接口的FutureTask类,再通过new Thread(futureTask).start()
即可开启新的线程。
Lock锁
- ReentrantLock
- ReentrantReadWriteLock.ReadLock
- ReentrantReadWriteLock.WriteLock
synchronized和lock的区别
- synchronized是内置的Java关键字;lock是一个Java类
- synchronized无法判断获取锁的状态;lock可以判断是否获得到了锁
- synchronized会自动释放锁;lock必须要手动释放锁,否则会出现死锁
- synchronized是可重入锁,不可以中断,非公平;lock,可重入锁,可以判断锁,非公平
并发下ArrayList不安全的解决方案
-
List<String> list = new Vector<>();
-
List<String> list = Collections.synchronizedList(new ArrayList<>());
-
List<String> list = new CopyOnWriteArrayList<>();
(写入时复制,修改后再覆盖)
并发下HashSet不安全的解决方案
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
并发下HashMap不安全
ConcurrentHashMap
工具类
- CountDownLatch
countDownLatch.countDown()
countDownLatch.await()
- CyclicBarrier
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("神龙出没"));
cyclicBarrier.await()
- Semaphore
semaphore.acquire()
获得信号,如果信号量已经满了,等待,等待被释放semaphore.release()
释放信号,信号量-1,然后唤醒等待的线程
ReadWriteLock(接口)
- 实现类
ReentrantReadWriteLock
BlockingQueue(接口)
-
ArrayBlockingQueue
方式 抛出异常 有返回值,不抛出异常 阻塞 等待 超时等待 添加 add offer put offer 移除 remove poll take poll 检测队首元素 element peek -
SynchronousQueue(同步队列)
- put进一个元素后,只能take后,才能继续put元素
线程池(三大方法、7大参数、4种拒绝策略)
-
优点
- 减少资源消耗
- 提高系统性能
- 方便管理
-
线程池创建方式
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。Executors返回的线程池对象弊端:
-
newFixedThreadPool和newSingleThreadExecutor
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
-
newCachedThreadPool和newScheduledThreadPool
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
-
-
三大方法
- Executors.newSingleThreadExecutor() 单个线程
- newFixedThreadPool(5) 固定线程池大小
- newCachedThreadPool() 可伸缩线程池大小
-
7大参数
-
corePoolSize 核心线程池大小
-
maximumPoolSize 最大核心线程池大小
线程池的最大大小如何设置
-
CPU 密集型,几核,就是几,可以保持CPU的效率最高
-
IO 密集型 判断你的程序中十分耗IO的线程 x 2
-
-
keepAliveTime 存活时间
-
unit 超时单位
-
workQueue 阻塞队列
-
threadFactory 线程工厂,创建线程的,一般默认不用管
-
handler 拒绝策略
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; }
-
-
4种拒绝策略
- new ThreadPoolExecutor.AbortPolicy() 所有的线程都在处理任务,并且阻塞队列也满了,就会抛出异常
- new ThreadPoolExecutor.CallerRunsPolicy() 被拒绝的任务在调用线程的方法中运行,不抛出异常
- new ThreadPoolExecutor.DiscardPolicy() 被拒绝的任务会被抛弃,不抛出异常
- new ThreadPoolExecutor.DiscardOldestPolicy() 抛弃最早的未被处理的进入线程池的任务,并尝试重新执行当前的新任务,不抛出异常
函数式接口
- Function函数式接口 有一个输入参数,有一个输出
- Predicate断定型接口 有一个输入参数,返回值只能是 布尔值
- Consumer消费型接口 只输入不返回
- Supplier供给型接口 只返回不输入
ForkJoin
-
特点:工作窃取
有的线程把自己队列中的任务干完之后,会去其他线程的队列中窃取一个任务执行,通常使用双端队列,被窃取任务的线程从双端队列头部拿任务执行,窃取任务的线程从双端队列尾部拿任务执行
- 优点:充分利用线程进行并行计算,减少线程间的竞争
- 缺点:消耗更多系统资源,只有一个任务时会出现竞争
-
主要步骤
- 分割任务:需要一个fork类把大任务拆成子任务,子任务太大可能还要分割
- 执行任务并合并结果:子任务在双端队列中执行,结果统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据
-
ForkJoinTask
:创建fork-join任务,提供了fork()和join()实现机制,通常情况下继承它的子类即可: -
RecursiveAction
(无返回值),RecursiveTask
(有返回值) -
ForkJoinPool
:执行fork-join任务
异步任务
-
异步执行、成功回调、失败回调
-
CompletableFuture
使用
- CompletableFuture.runAsync()
- CompletableFuture.supplyAsync()
JMM
java内存模型,不存在的东西,一种概念
- 线程解锁前,必须把共享变量立刻刷新回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一把锁
线程 工作内存、主内存
-
8种操作
-
lock unlock
-
read load
-
use assign
-
write store
-
Volatile
Volatile是Java虚拟机提供的轻量级同步机制
作用
-
保证可见性(保证线程知道主内存中值得变化)
-
不保证原子性
如果不使用lock和synchronized,怎么保证原子性
- 使用原子类 AtomicInteger类
-
禁止指令重排
- 增加内存屏障,保证特定的操作的执行顺序
- 保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
单例模式
- 饿汉式
- 懒汉式、DCL懒汉式
- 静态内部类写法
单例不安全 -> 反射 -> 枚举类
CAS
-
比较并交换
比较当前工作内存中的值和主内存中的值,如果这个值是期望的,就执行操作,否则就一直循环
-
缺点
- 循环会耗时(自旋锁)
- 一次只能保证一个共享变量的原子性
- ABA问题 -> 原子引用(AtomicStampedReference)(乐观锁)
各种锁
公平锁、非公平锁
- 公平锁:非常公平,不能插队,必须先来后到
- 非公平锁:非常不公平,可以插队(默认都是非公平锁)
可重入锁(递归锁)
拿到了外面的锁就可以拿到里面的锁
自旋锁
myLock()方法利用的CAS,当第一个线程A获取锁的时候,能够成功获取到,不会进入while循环,如果此时线程A没有释放锁,另一个线程B又来获取锁,此时由于不满足CAS,所以就会进入while循环,不断判断是否满足CAS,直到A线程调用myUnLock方法释放了该锁,线程B才会退出循环。
死锁
两个线程互相持有对方需要的资源
package com.shayne.deadlock;
/**
* 死锁例子
*/
public class DeadLockDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
}
}
class MyThread implements Runnable{
Object A = new Object();
Object B = new Object();
boolean flag = true;
@Override
public void run() {
if (flag) {
flag = false;
a();
} else {
b();
}
}
public void a() {
synchronized (A) {
System.out.println(Thread.currentThread().getName() + " get A lock...");
b();
}
}
public void b() {
synchronized (B) {
System.out.println(Thread.currentThread().getName() + " get B lock...");
a();
}
}
}
chronized (A) {
System.out.println(Thread.currentThread().getName() + " get A lock…");
b();
}
}
public void b() {
synchronized (B) {
System.out.println(Thread.currentThread().getName() + " get B lock...");
a();
}
}
}