虚假唤醒

当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功
1.比如说买货,如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了
,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁

解决:使用while替换if,即使唤醒判断之后,下次被唤醒仍会进行条件判断

CopyOnWrite

写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种通用优化策略。其核心思想是,如果有多个调用者(Callers)同时访问相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

通俗易懂的讲,写入时复制技术就是不同进程在访问同一资源的时候,只有更新操作,才会去复制一份新的数据并更新替换,否则都是访问同一个资源。

常用的辅助类

CountDownLatch:倒计数器

CyclicBarrier:正计数器

Semaphore:(PV)信号量,acquire和release管理

ReadWriteLock:读写锁,写锁只能被一个线程占有,读锁可以被多个线程占有,读写不能共存

线程池

【强制】线程池不允许使用 Executors去创建,而是通过 ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors返回的线程池对象的弊端如下:

1)FixedThreadPool和SingleThreadPool:

​ 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2)CachedThreadPool和ScheduledThreadPool:

允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

Executors三大方法:

1
2
3
4
5
Executors.newSingleThreadExecutor();// 单个线程 

Executors.newFixedThreadPool(5); // 创建一 个固定的线程池的大小

Executors.newCachedThreadPool();//非固定大小线程

Executors的方法本质上是对ThreadPoolExecutor的封装,源码如:

1
2
3
4
5
6
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

ThreadPoolExecutor七大参数:

1
2
3
4
5
6
7
8
9
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
int maximumPoolSize, // 最大核心线程池大小
long keepAliveTime, // 超时了没有人调用就会释放
TimeUnit unit, // 超时单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂:创建线程的,一般
不用动
RejectedExecutionHandler handle // 拒绝策略)
{}

拒绝策略:

1
2
3
4
new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!
new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!

函数式接口

  • 函数式接口: 只有一个方法的接口

Function<String,String> function = (输入值)->{return 输出值;};

  • 断定型接口:有一个输入参数,返回值只能是 布尔值

Predicate<String> predicate = (str)->{return str.isEmpty(); };

  • Consumer 消费型:只有输入,没有返回

Consumer<String> consumer = (str)->{System.out.println(str);};

  • Supplier 供给型:没有输入,只有返回值

Supplier supplier = ()->{ return 1024; };

完整代码参考(没用lambda):

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Demo04 {
public static void main(String[] args) {
// Supplier supplier = new Supplier<Integer>() {
// @Override
// public Integer get() {
// System.out.println("get()");
// return 1024;
// }
// };
Supplier supplier = ()->{ return 1024; };
System.out.println(supplier.get());
}
}

Stream流计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 题目要求:一分钟内完成此题,只能用一行代码实现!
* 现在有5个用户!筛选:
* 1、ID 必须是偶数
* 2、年龄必须大于23岁
* 3、用户名转为大写字母
* 4、用户名字母倒着排序
* 5、只输出一个用户!
*/
public class Test {
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(2,"b",22);
User u3 = new User(3,"c",23);
User u4 = new User(4,"d",24);
User u5 = new User(6,"e",25);
// 集合就是存储
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
// 计算交给Stream流
// lambda表达式、链式编程、函数式接口、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);
}
}

异步回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//异步的异常捕获并作出相应的处理
public class Demo01 {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
CompletableFuture<Integer> completableFuture =
CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
int i = 10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((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; // 可以获取到错误的返回结果
}).get());
/**
* succee Code 200
* error Code 404 500
*/
}
}

JMM

区分:java内存结构(堆栈等)

JMM就是Java内存模型(java memory model),java内存模型(JMM)屏蔽掉各种硬件和操作系统的内存访问差异,以实现让java程序在各种平台下都能达到一致的并发效果。Java内存模型规定所有的变量都存储在主内存中,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行线程不能直接读写主内存中的变量

Volatile 是 Java 虚拟机提供轻量级的同步机制

1、保证可见性

2、不保证原子性

3、禁止指令重排

关于JMM的一些同步的约定:

1、线程解锁前,必须把共享变量立刻刷回主存。

2、线程加锁前,必须读取主存中的最新值到工作内存中!

3、加锁和解锁是同一把锁

为何i++无法保证原子性?先读取i的值,再+1,最后赋值到i,三步操作。i=j同理。

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态

unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量

才可以被其他线程锁定

read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便

随后的load动作使用

load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中

use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机

遇到一个需要使用到变量的值,就会使用到这个指令

assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变

量副本中

store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中, 以便后续的write使用write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write

不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存不允许一个线程将没有assign的数据从工作内存同步回主内存

一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作

一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁

如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值

如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量

对一个变量进行unlock操作之前,必须把此变量同步回主内存

指令重排(有序性?)

什么是指令重排:你写的程序,计算机并不是按照你写的那样去执行的。

源代码—>编译器优化的重排—> 指令并行也可能会重排—> 内存系统也会重排—-> 执行

处理器在进行指令重排的时候,考虑:数据之间的依赖性!

volatile:保证有序性->内存屏障->避免指令重排

单例模式

线程安全版,防反射锁类构造器,防并发锁实例构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class LazyMan {
private static boolean flag = false;
private LazyMan(){
synchronized (LazyMan.class){
if (qinjiang == flag){
flag = true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static LazyMan lazyMan;
// 双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); // 不是一个原子性操作
}
}
}
return lazyMan;
}

CAS

比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环(底层是自旋锁)

缺点:

1、 循环会耗时

2、一次性只能保证一个共享变量的原子性

3、ABA问题 (改了又改回去,用版本号(时间戳)解决)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

## 可重入锁

拿到一个锁,当前程序里面的锁也能拿到

```java
package com.kuang.lock;
import javax.sound.midi.Soundbank;
// Synchronized
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName() + "sms");
call(); // 这里也有锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName() + "call");
}
}

出现死锁怎么办?

打开idea终端:

  1. 使用 jps -l 定位进程号
  2. 使用 jstack 进程号 找到死锁问题