目录
注意: CopyOnWriteArraySet 内部由CopyOnWriteArrayList构成
Lock
1. 监视器实现精准唤醒
同一把锁, 设置多个监视器(A,B, C。由A 唤醒 B , B唤醒C, C 唤醒A。轮流工作。其中一个工作时,其他线程等待。
代码
public class D {
public static void main(String[] args) {
Data4 data = new Data4();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printC();
}
},"C").start();
}
}
class Data4{ // 资源类 Lock
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; // 1A 2B 3C
public void printA(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number!=1){
// 等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAAAAAA");
// 唤醒,唤醒指定的人,B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB");
// 唤醒,唤醒指定的人,c
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
// 业务,判断-> 执行-> 通知
while (number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB");
// 唤醒,唤醒指定的人,c
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
2.公平锁
- 公平锁:加锁前先查看是否有排队等待的线程,有的话优先处理排在前面的线程,先来先得。
- 非公平所:线程加锁时直接尝试获取锁,获取不到就自动到队尾等待。
- Lock 默认是 非公平锁, 其有构造函数,可以设定为公平锁
- synchronized 是非公平锁
3.synchronized 和 Lock 的区别
4. 8锁问题
首先:8锁问题是一个问题。就是锁是谁,哪一把锁的问题。
1.搞清楚锁是什么,用的哪一把锁,该方法需要等哪一把锁。
2.This 锁, class 锁, 自定义对象锁。不是一个锁不遵守一个同步。
集合类不安全
集合类不安全是由于 修改值本身,和修改size 不是原子的。多个线程的添加方法可能在同一个位置add.
1.HashSet的本质就是一个HashMap
- 所有的Set 其本质都是一个 对应类型的Map . Set中的 值, 就i是 Map 中的 key. value 是一个 统一的对象 PRESENT(Object)
2.List 不安全的解决
// 1、使用 Vcector 效率低
List<String> list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new Vector<>();
// 2、使用 Collections 工具类转换。
List<String> list = Collections.synchronizedList(new ArrayList<>());
// 3。 使用 写入时复制, 实现读写分离
List<String> list = new CopyOnWriteArrayList<>();
- Vector是增删改查方法都加了synchronized,
- 保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,
- Collections : 使用装饰模式,返回一个安全的List.
- CopyOnWriteArrayList 只是在增删改上加锁,
- 但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况
- 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array
- 该集合详解
3. Set不安全解决
// Collections 包装
Set<String> set = Collections.synchronizedSet(new HashSet<>());
// 写入时复制
Set<String> set = new CopyOnWriteArraySet<>();
注意: CopyOnWriteArraySet 内部由CopyOnWriteArrayList构成
构造器
特性
1. 它最适合于具有以下特征的应用程序:Set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。
2. 它是线程安全的。
3. 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。
4. 迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等 操作。
5. 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。6. 该集合详解
4.Map 不安全的解决
- 加载因子:默认为 0.75
- 默认初试容量: 1 << 4 16.
// 同步 Map 使用分段锁
Map<String, String> map = new ConcurrentHashMap<>();
Hashtable线程安全但效率低下
分段锁 :
Callable
1.与Runnable差别
1、可以有返回值
2、可以抛出异常
3、方法不同,run()/ call()
创建Callable实现类,丢入 FutureTask
创建FutureTask (Runnable的实现类),丢入线程.
Callable 的 call 方法是线程的任务实体
注意:
1.同一个FutureTask 实例, 只能被线程执行一次。一个 task 即视new 多个Thread 也只会执行一次 该task。
2. get 方法获取返回值可能会阻塞当前线程。
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread); // 适配类
new Thread(futureTask,"A").start();
// 注意: 一个 FutureTask的实例,只会 被线程执行一次。
new Thread(futureTask,"B").start(); // 结果会被缓存,效率高
//这个get 方法可能会产生阻塞!把他放到最后
Integer o = (Integer) futureTask.get();
// 或者使用异步通信来处理!
System.out.println(o);
System.out.println("main");
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("call()"); // 会打印几个call
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 耗时的操作
return 1024;
}
}
同一个FutureTask 实例, 只能被线程执行一次
FutureTask中有一个state属性,创建FutureTask时为0,执行一次该线程后发生变化。也只有是0的时候才会开启线程去执行他
源码
Stateoffset 在Java中没有实际意义,但在c中表示 state的地址。Stateoffset 通过本地方法获取state的地址,再通过本地方法加Stateoffset 去修改state的值。上边if是实现了值的交换。这里,通过本地犯法改变了State.
常用的辅助类
CountDownLatch
线程减法计数器。
在await的线程中进行阻塞。直到countdown()在其他线程中完成计数,才唤醒。
应用场景
对比 关键路径算法,要执行一个任务,必须等待其前驱任务执行完毕,再执行该任务,而在多线程下,就是要将多个线程执行完毕后,当前线程才能继续向下执行。其实就是一个集中等待点,人齐了再发出
代码实例
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 总数是6,必须要执行任务的时候,再使用!
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" Go out");
countDownLatch.countDown(); // 数量-1
},String.valueOf(i)).start();
}
countDownLatch.await(); // 等待计数器归零,然后再向下执行
System.out.println("Close Door");
}
}
在主线程中设置等待点,直到分线程的指定任务完成,完成计数。再唤醒主线程
CyclicBarrier
加法计数器
实例
public class CyclicBarrierDemo {
public static void main(String[] args) {
/**
* 集齐7颗龙珠召唤神龙
*/
// 召唤龙珠的线程
// lamada : 在统一阻塞点插入任务插入任务。
CyclicBarrier cyclicBarrier = new CyclicBarrier(8,()->{
System.out.println("召唤神龙成功!");
});
// 开启 7 个 线程
for (int i = 1; i <=7 ; i++) {
final int temp = i;
// lambda能操作到 i 吗
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
try {
cyclicBarrier.await(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
创建计数器时,
1. 第一参数, 设定 触发线程数
2. 第二参数 设定 插入任务, 是一个lamada .
运行:
1. 在 多个线程中执行 await , 对线程进行阻塞,
2. 当阻塞线程数 达到 8 后, 通过新的线程 执行 lamada 插入任务。
Semaphore
信号量(通行证) 限流
semaphore.acquire(); // 获取则进入, 否则就阻塞等待
semaphore.release(); // finally 进行释放。
资源有限,谁拿到资源谁执行。
例
public class SemaphoreDemo {
public static void main(String[] args) {
// 线程数量:停车位! 限流!
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
// acquire() 得到
try {
// 获取
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放
semaphore.release(); // release() 释放
}
},String.valueOf(i)).start();
}
}
读写锁
读可以一起读,写只能有一个写 , 共享锁
示例
class MyCacheLock{
private volatile Map<String,Object> map = new HashMap<>();
// 读写锁: 更加细粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock lock = new ReentrantLock();
// 存,写入的时候,只希望同时只有一个线程写
public void put(String key,Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
// 取,读,所有人都可以读!
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
ArrayBlockingQueue 阻塞队列
4组API
构造方法要 给定 初始化容量。
有异常: add ,remove element (List 接口的)
有返回值无异常:offer, poll ,peel (队列接口的)
阻塞等待 : put() take() 队列满时,则在等待队列等待
超时等待:offer() poll() 设定超时时间。
SynchronousQueue 同步队列
没有容量,
进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
put、take::: 放一个,就必须取出来一个,才能继续放,否则就阻塞。
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>(); // 同步队列
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+" put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+" put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
线程池
线程池:三大方法、7大参数、4种拒绝策略
线程复用、可以控制最大并发数、管理线程。
Executors 工具类
封装集成了很多用于初始化线程池的方法。 但是不推荐使用
阿里规范
ExecutorService executorsPool01 = Executors.newCachedThreadPool();
ExecutorService executorService = Executors.newFixedThreadPool(8);
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
源码解析
通过Executors创建线程池的本质就是 通过 ThreadPoolExecutor线程池
ThreadPoolExecutor 线程池
使用
public class Demo01 {
public static void main(String[] args) {
// 自定义线程池!工作 ThreadPoolExecutor
// 最大线程到底该如何定义
// 1、CPU 密集型,几核,就是几,可以保持CPu的效率最高!
// 2、IO 密集型 > 判断你程序中十分耗IO的线程,
// 程序 15个大型任务 io十分占用资源!
// 获取CPU的核数
System.out.println(Runtime.getRuntime().availableProcessors());
List list = new ArrayList();
ExecutorService threadPool = new ThreadPoolExecutor(
2,
Runtime.getRuntime().availableProcessors(),
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()); //队列满了,尝试去和最早的竞争,也不会抛出异常!
try {
// 最大承载:Deque + max
// 超过 RejectedExecutionException
for (int i = 1; i <= 9; i++) {
// 使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
七大参数
maximumPoolSize : // 核心线程池大小
keepAliveTime : // 线程挂起时间。超时就销毁
TimeUnit unit :// 时间单位
BlockingQueue<Runnable> workQueue : //阻塞队列
ThreadFactory threadFactory : //线程工厂
RejectedExecutionHandler handler : //拒绝策略
源码
线程数
注意:核心线程保持存活, 当阻塞队列满时,每多出一个线程任务,则多创建一个 线程,直到Max.
最大线程数 : max + queue. 多出的线程任务,只能等前边的任务执行完获取到线程去执行,或者放弃任务。
拒绝策略
* new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常 * new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里! * new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常! * new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!
AbortPolicy : 超出最大任务数(max + queue)抛出异常。
DiscardPolicy : 超出最大任务数(max + queue)丢弃任务
DiscardOldestPolicy : 尝试 插队,直接去获取线程(恰好此时有任务结束,太巧了) 失败则放弃。
CallerRunsPolicy : 比较难理解, 由创建该任务的线程来执行。即不再使用其他线程执行该任务,
直接在该位置就地执 行该任务,相当于在该位置直接插入任务。
异常:RejectedExecutionException
CallerRunsPolicy
for (int i = 1; i <= 300; i++) {
// 使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
上边线程任务若获取线程失败,则 在这里就地执行该任务,相当于调用了该方法。
System.out.println(Thread.currentThread().getName() + "11111111111");
}
pool-1-thread-7 ok 子线程调用
main11111111111 主线程本身任务
main11111111111
main11111111111
main11111111111
main ok 输出ok任务插入到main
main11111111111
pool-1-thread-8 ok
pool-1-thread-5 ok
获取线程失败,则相当于main 调用了一个函数,等函数执行完,才返回main函数
执行前后线程的状态
1、核心线程 且 allowCoreThreadTimeOut 为false
那核心线程在执行任务完后,会处于阻塞blocking状态,直到下一个任务取出它
2、核心线程 且 allowCoreThreadTimeOut 为true
核心线程如果在keepAliveTime时间内没有新的任务,超时时间之前,处于阻塞状态blocking,超时时间到了后,线程状态将变为dead(terminated)
3、非核心线程
超时时间之前阻塞状态blocking,超时时间之后,dead(terminated)
4、如果线程池都关闭了,那所有的线程都会变为dead(terminated)
四大函数式接口
四大函数式接口,其实就是 官方给的 lamada 接口的几个常用模板
Function
一个参数,一个返回值
使用
Function<String,String> function = str->{return str;}; System.out.println(function.apply("asd"));
public interface Function<T, R> { R apply(T t); }
Predicate
断定型接口 一个参数,一个boolean返回值
使用
Predicate<String> predicate = (str)->{return str.isEmpty(); }; System.out.println(predicate.test("")); public interface Predicate<T> { boolean test(T t); }
Consumer
消费型接口,只进不出,一个参数,无返回值。
foreach(Consumer con) : 对 列表中每一个元素作为Consumer 的参数,去执行accrpt()
使用
Consumer<String> consumer = (str)->{System.out.println(str);}; consumer.accept("sdadasd"); public interface Consumer<T> { void accept(T t); }
Supplier
供给型接口 无参数, 带一个返回值 通过get可以获取到一个值。
使用
Supplier supplier = ()->{ return 1024; }; System.out.println(supplier.get()); public interface Supplier<T> { T get(); }
Stream流式计算
链式调用
list.stream()
.filter(u->{return u.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);
Stream<T> filter(Predicate<? super T> predicate);
filter 使用 Predicate 进行判断, 从泛型可以看到,返回的的流的类型T, 就是 Predicate接收的参数类型。
流入类型,就是 流出类型
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
map 使用 Function 进行映射, 从泛型看到,返回的流的类型,是Function的返回值类型。而原流的类型是Function的参数类型。
使用Function 的 参数和返回值, 实现 流入类型到流出类型的映射。
Stream<T> sorted(Comparator<? super T> comparator);
传入一个比较器
Stream<T> limit(long maxSize);
传入一个 maxSize , 返回原流的一部分
void forEach(Consumer<? super T> action);
传入一个 Consumer , 只进不出。 对每一个输入流的 元素,执行动作。
ForkJoin
工作窃取
将共组分组,每组都是一个双端队列, 当一个线程把自己的任务执行完以后,就去执行另一个队列中的任务,从反向执行。
推荐写法: 推荐使用 invokeAll 而不是 fork
public class Main {
public static void main(String[] args) throws Exception {
// 创建2000个随机数组成的数组:
long[] array = new long[2000];
long expectedSum = 0;
for (int i = 0; i < array.length; i++) {
array[i] = random();
expectedSum += array[i];
}
System.out.println("Expected sum: " + expectedSum);
// fork/join:
ForkJoinTask<Long> task = new SumTask(array, 0, array.length);
long startTime = System.currentTimeMillis();
Long result = ForkJoinPool.commonPool().invoke(task);
long endTime = System.currentTimeMillis();
System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
}
static Random random = new Random(0);
static long random() {
return random.nextInt(10000);
}
}
class SumTask extends RecursiveTask<Long> {
static final int THRESHOLD = 500;
long[] array;
int start;
int end;
SumTask(long[] array, int start, int end) {
this.array = array;
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start <= THRESHOLD) {
// 如果任务足够小,直接计算:
long sum = 0;
for (int i = start; i < end; i++) {
sum += this.array[i];
// 故意放慢计算速度:
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
return sum;
}
// 任务太大,一分为二:
int middle = (end + start) / 2;
System.out.println(String.format("split %d~%d ==> %d~%d, %d~%d", start, end, start, middle, middle, end));
SumTask subtask1 = new SumTask(this.array, start, middle);
SumTask subtask2 = new SumTask(this.array, middle, end);
invokeAll(subtask1, subtask2);
Long subresult1 = subtask1.join();
Long subresult2 = subtask2.join();
Long result = subresult1 + subresult2;
System.out.println("result = " + subresult1 + " + " + subresult2 + " ==> " + result);
return result;
}
}
fork
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);// 提交任务
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));
}
protected Long compute() {
// 这里是递归出口,不能少
if ((end-start)<temp){
Long sum = 0L;
for (Long i = start; i <= end; i++) {
sum += i;
}
return sum;
}else { // forkjoin 递归
long middle = (start + end) / 2; // 中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
task1.fork(); // 拆分任务,把任务压入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
task2.fork(); // 拆分任务,把任务压入线程队列
return task1.join() + task2.join();
}
}
异步回调
// 在这里开启异步。 该任务的执行,可以看作是另一个线程在执行,不会阻塞当前线程。
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
int sum = 0;
for(int i = 0; i < 10000000; i++){
sum+= i;
}
int i = 10/1;
return sum;
});
// 回调部分, 当上边异步完成后,在这里接收处理结果。 该位置也不是阻塞的
// whenComplete 返回自身的实例。
completableFuture.whenComplete((t, u) -> {
// 结果处理。 t : 异步的返回值。 u : 错误信息
System.out.println("t=>" + t); // 正常的返回结果
System.out.println("u=>" + u); // 错误信息:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
// 异常处理
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 233; // 可以获取到错误的返回结果
});
// completableFuture.get()
CompletableFuture.supplyAsync : 有返回值的 开启 异步 的方法。 该方法不会阻塞当前线程。
CompletableFuture.runAsync : 无返回值的异步。
whenComplete : 异步任务执行完之后将会执行。 不会阻塞当前线程。
exceptionally : 异步任务的异常处理, 不会阻塞当前线程。
以上4个任务的执行,都和当前线程无关。
completableFuture.get() : 该方法在当前先线程获取返回值, 会阻塞当前线程