JUC多线程并发
1、JUC是什么?
java.util.concurrent在并发编程中使用的工具类。
2、进程和线程
线程、进程,如果不能使用一句话说出来的技术不扎实。
进程:一个程序可以包含多个线程,程序的集合。
线程:开了一个进程。一个线程负责一个功能。 自动保存啥的(线程负责的)
JAVA:Thread、Runnable、Callable
java真的可以开启线程吗 不可以
本地方法,底层的是C++,java无法直接操作硬件
并发、并行
System.out.println(Runtime.getRuntime().availableProcessors());
8个
并发编程的本质:充分利用CPU的资源
线程有几个状态
wait和sleep
1、来自不同的类
wait=>Object
sleep=>Thread
企业中。休眠,
2、关于锁的释放
wait会释放锁,
sleep抱着锁睡觉,不会释放
3、适用范围不同
wait
sleep
4、是否需要捕获异常
wait 不需要
sleep 需要
3、Lock锁(重点)
传统Synchronized
线程是一个单独的资源类,没有任何附属的才足以,
多线程操作一个资源。资源类是OOP
OOP资源类
1、LOCK锁
LOCK锁
2、公平锁和非公平锁
Synchronized和Lock区别
-
Synchronized 内置的java关键字,lock是一个java类
-
Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
-
Synchronized 会自动释放锁,自动挡。Lock必须要手动释放锁,如果不释放锁,死锁
-
Synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等);lock锁就不一定会等待下去。 lock.trylock()
-
Synchronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,非公平么(可以自己设置)
-
Synchronized 适合锁少量的代码问题,Lock适合锁大量的同步代码。
锁是什么,如何判断锁是谁的?
4、生产者和消费者问题
Synchronized版 wait notify
juc lock
大有门道
判断用while 不能用if ,超过两个线程就不安全
面试:单例模式,排序算法,生产者和消费者,死锁
问题存在。A B C D 4个线程!虚假唤醒
If 改为 while if 判断一次 while一直判断
JUC版的生产者和消费者问题
通过Lock 找到Condition
快捷键-----
任何一个新的技术,绝对不是仅仅只覆盖原来的技术,也带了优势和补充
Condition 精确的通知和唤醒线程
精准唤醒
5、8锁现象
如何判断锁的是谁。永远的知道什么锁,锁到底锁的是谁。
对象,Class
小结
new this 具体的一个手机
static Class 唯一的模板
6、集合类不安全
java.util.ConcurrentModificationException 并发修改异常
List 不安全
List 不安全
三个安全方法
1.vector synchronized
2.collections.synchronizedList
3.CopyOnWriteArrayList lock
cow 优化策略
set 不安全
set 不安全
1.collections.synchronizedSet
2.copyonwriteArraySet
hashSet的底层是什么?
//无参构造
public HashSet() {
map = new HashMap<>();
}
//添加 add set 本质就是map key 是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
PRESENT //不变的值
private static final Object PRESENT = new Object();
//
map 不安全
map 不安全
也有collections.synchronizedMap
7、Callable(简单)
细节:
1、有缓存
2、结果可能需要等待,会阻塞!
8、常用的辅助类
8.1、CountDownLatch
原理:
CountDownLatch.countDown()
//数量 减1
CountDownLatch.await()
//等待计数器归零,然后再向下执行
8.2、CyclicBarrier
加法计数器
8.3、Semaphore
信号量
semaphore acquire 假设满了。 等待,等待被释放为止
semaphore.release 释放量+1,然后唤醒等待的线程
9、读写锁 ReadWriteLock
独占锁(写锁) 一次只能被一个线程占有
共享锁(读锁)多个线程可以同时占有
ReadWriteLock
读-读 可以共存
读-写 不能共存
写-写 不能共存
10、阻塞队列 BlockingQueue
阻塞
BlockingQueue
BlockingQueue 不是新东西
Queue
队列
什么情况会使用阻塞队列?
多线程并发,线程池用队列维护。
学会使用队列
添加、移除
四组API
方式 | 抛出异常 | 有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer() | put() | offer(…) |
移除 | remove | poll() | take() | poll(…) |
检测队首元素 | element | peek | – | – |
1、抛出异常
2、不会抛出异常
3、阻塞等待
4、超时等待
SynchronousQueue 同步队列
SynchronousQueue 同步队列
没有容量。
进去一个元素,必须等待取出来以后,才能再往里面放一个元素。
put take
11、线程池(重点) ThreadPool
线程池:三大方法、7大参数、4种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!优化资源的使用!=>池化技术
线程池、连接池、内存池、对象池、、
池化技术:事先准备好一些资源,有人要用,就可以来这里拿,用完之后还给这里。
线程池的好处:
- 降低资源的消耗
- 提高响应的速度
- 方便管理
线程复用、可以控制最大并发数、管理线程
1、三大方法
线程池:三大方法
使用Executors 容易OOM
FixedThreadPool 固定线程
SingleThreadPool 单线程
请求过多, Interger.MAX_VALUE 约21亿
堆积大量请求OOM
CacheThreadPool 伸缩 和 ScheduledThreadPool
允许创建的线程数量过大,约21亿,创建大量线程导致OOM
package com.junlin.lock8;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo1 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个
// ExecutorService threadPool = Executors.newFixedThreadPool(5);//固定
ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩
try {
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" OK ");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
2、7大参数
7大参数
源码分析
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//本质 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;
}
7大参数 的 银行例子
3、手动创建一个线程池
手动创建一个线程池
四种拒绝策略
4、小结
小结
最大线程 该如何定义
- CPU密集型,几核就是设置几,可以保持CPU的效率最高
获取CPU的核数。
- IO密集型
> 大于 判断 你程序中十分耗IO的线程
池的最大大小该如何去设置
12、四大函数式接口(必须掌握)
新时代的程序员: lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口 为了 简化 编程模型!!!!!!!!
函数式接口:只有一个抽象方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// 泛型,枚举,反射,和注解
//lambda表达式、链式编程、函数式接口、Stream流式计算
//超级多FunctionalInterface
//简化编程模型,在新版本的框架底层大量应用
//foreach(消费者类的函数式接口)
1、Function 函数型接口
package com.junlin.function;
import java.util.function.Function;
/**
* Function 函数型接口 有一个输入参数,有一个输出
* 只要是函数型接口,可以用lambda表达式简化
*/
public class Demo1 {
public static void main(String[] args) {
// Function function = new Function<String,String>() {
// @Override
// public String apply(String o) {
// return o;
// }
// }; //匿名表达类
Function function = (str)->{return str;}; //lambda 表达式
// Function function = str->str; // 继续 简化
System.out.println(function.apply("23423"));
}
}
2、Predicate
public interface Predicate<T> {
boolean test(T t);
}
//应用
package com.junlin.function;
import java.util.function.Predicate;
/**
* 断定型接口:有一个输入参数,返回值只能是 bool值
*/
public class Demo2 {
public static void main(String[] args) {
//判断字符串是否 为空
// Predicate<String> predicate = new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// };
Predicate<String> predicate = x -> x.isEmpty();
System.out.println(predicate.test(""));
}
}
3、Consumer 消费型接口
只花。
//只有传入,没有返回值
@FunctionalInterface
public interface Consumer<T> {
//只有传入,没有返回值
void accept(T t);
}
package com.junlin.function;
/**
* Consumer 消费型接口 : 只有输入,没有返回值
*/
import java.util.function.Consumer;
public class Demo3 {
public static void main(String[] args) {
// Consumer<String> consumer = new Consumer<String>() {
// @Override
// public void accept(String o) {
// System.out.println(o);
// }
// };
Consumer<String> consumer = x-> System.out.println(x);
consumer.accept("345");
}
}
4、Supplier 供给型接口
只给。
只有返回值,没有传值
供给型接口 只供给,不消费。
@FunctionalInterface
public interface Supplier<T> {
T get();
}
package com.junlin.function;
import java.util.function.Supplier;
/**
* Supplier 供给型接口 只供给,不消费。
*/
public class Demo4 {
public static void main(String[] args) {
// Supplier<String> supplier = new Supplier<String>() {
// @Override
// public String get() {
// return "1024";
// }
// };
Supplier<String> supplier = ()->"1024";
System.out.println(supplier.get());
}
}
13、Stream流式计算
什么是Stream流式计算
大数据:存储+计算
集合、Mysql 本质就是存储东西的;
计算都应该交给流来操作!
14、ForkJoin
什么是ForkJoin
分支合并
ForkJoin 在JDK1.7,并行执行任务!提高效率!大数据量!
大数据:Map Reduce(把大任务拆分为 小任务)
分而治之
ForkJoin 特点 工作窃取
代码测试
- 整体上 ForkJoinPool 是对 ThreadPoolExecutor 的一种补充
- ForkJoinPool 提供了其独特的线程工作队列绑定方式、工作分离以及窃取方式
- ForkJoinPool + ForkJoinTask 配合实现了 Fork/Join 框架
- 适用于任务可拆分为更小的子任务的场景(有点类似递归),适用于计算密集型任务,可以充分发挥 CPU 多核的能力
不同方法!!!!!!!!!!!
比较:
3000 普通程序员:
总和:500000000500000000-------耗时:436
6000 forkJoin 程序员:
总和:500000000500000000-------耗时:238
9000 程序员 流式并行计算:
总和:500000000500000000-------耗时:166
1、3000 普通程序员
代码:
//3000 普通程序员
public static void test1(){
long start = System.currentTimeMillis();
long sum = 0l;
for (long i = 1l; i <= 10_0000_0000; i++) {
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("总和:"+sum+"-------"+"耗时:"+(end - start));
}
时间和结果:
总和:500000000500000000-------耗时:436
2、6000 forkJoin 程序员
方法继承RecursiveTask
package com.junlin.forkjoin;
import java.util.concurrent.RecursiveTask;
/**
* RecursiveTask 有返回值的任务
* RecursiveAction 没有返回结果的任务
* CountedCompleter: 在任务完成执行后,触发自定义的钩子函数。
* 1.forkjoinpool 通过它来执行
* 2.计算任务 forkjoinpool.execute(forkjoinTask task)
* 3.计算类要继承 forkjoinTask
*/
public class ForkJoinDemo1 extends RecursiveTask<Long> {
private Long Start;
private Long End;
private Long Max = 10000L;
//构造函数 丢入 首尾数字
public ForkJoinDemo1(Long Start,Long End){
this.Start = Start;
this.End = End;
// this.Max = Max;
}
@Override
protected Long compute() {
if((End - Start) < Max){
long sum = 0L;
for (long i = Start; i <= End ; i++) {
sum += i;
}
return sum;
}else {
//否则就进入 分支合并 forkJoin
long middle = (End + Start)/2;
//任务一分为2 任务1
ForkJoinDemo1 task1 = new ForkJoinDemo1(Start,middle);
//拆分任务,把任务压入线程队列
// 内部入队 和外部入队 用 ForkJoinPool维护 工作队列
task1.fork();
//任务2,task2
ForkJoinDemo1 task2 = new ForkJoinDemo1(middle+1,End);
task2.fork();
//爆红 缺少 约束
return task1.join() + task2.join();
}
}
}
实现test
//6000 程序员 forkjoin
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
//new 一个 ForlJoinPool
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo1(0l,10_0000_0000l);
//forkJoinPool.execute(); 没有返回值
//forkJoinPool.submit 有返回值 可以get
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
//提交后 get
long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("总和:"+sum+"-------"+"耗时:"+(end - start));
}
//声明 ForkJoinPool 。
//继承实现 ForkJoinTask 抽象类或其子类,在其定义的方法中实现你的业务逻辑。
//子任务逻辑内部在合适的时机进行子任务的 fork 拆分。
//子任务逻辑内部在合适的时机进行 join 汇总
总和:500000000500000000-------耗时:238
3、9000 Stream流式计算
使用 longstream
parallel 并行的意思
parallel 并行
java8除了新增stream,还提供了parallel stream-多线程版的stream,parallel stream的优势是:充分利用多线程,提高程序运行效率,
reduce 归纳 归约运算 identity 初始值 本体。
//9000 程序员 流式并行计算
public static void test3(){
long start = System.currentTimeMillis();
//intStream doubleStream longStream
//range() 闭区间 rangeClosed 左闭右开
long sum = LongStream.rangeClosed(1L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("总和:"+sum+"-------"+"耗时:"+(end - start));
}
结果如下
总和:500000000500000000-------耗时:166
15、异步回调 Future
Future 设计的初衷:对将来的某个事件的结果进行建模。 ajax
可以做很多事。
CompletableFuture 实现类
completable 可完备化的;
package com.junlin.future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep((2));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "runAsync=>void");
});
System.out.println("11111");
completableFuture.get();//获取阻塞结果
}
}
16、JMM
请你谈谈对Volatile的理解
Volatile是Java虚拟机提供轻量级的同步机制 易变的; 无定性的; 无常性的; 可能急剧波动的; 不稳定的; 易恶化的; 易挥发的; 易发散的
1、保证可见性
2、不保证原子性
3、禁止指令重排
什么是JMM
JMM: java 内存模型,不存在的东西,只是一个概念!亦是约定!
关于JMM的一些同步的约定:
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 线程解锁前,必须把共享变量立刻刷回主存。
- 加锁和解锁是同一把锁。
8种操作
在Java中JMM内存模型定义了八种操作来实现同步的细节。
- read 读取,作用于主内存把变量从主内存中读取到本本地内存。
- load 加载,主要作用本地内存,把从主内存中读取的变量加载到本地内存的变量副本中
- use 使用,主要作用本地内存,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。、
- assign 赋值 作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store 存储 作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
- write 写入 作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
- lock 锁定 :作用于主内存的变量,把一个变量标识为一条线程独占状态。
- unlock 解锁:作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
所以看似简单的通信其实是这八种状态来实现的。
同时在Java内存模型中明确规定了要执行这些操作需要满足以下规则:
- 不允许read和load、store和write的操作单独出现。
- 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
- 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,lock和unlock必须成对出现
- 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
- 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。
所以上面说的操作要严格执行。
问题:
程序不知道 主内存的num 被修改过了 所以一直还在执行。
package com.junlin.tvolatile;
import java.util.concurrent.TimeUnit;
public class Demo01 {
private static int num = 0;
public static void main(String[] args) {
new Thread(()->{
while(num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
17、Volatile
1、保证可见性
是指 保证 线程A 可见。主存的变量发生了变化。同步机制之一。
package com.junlin.tvolatile;
import java.util.concurrent.TimeUnit;
public class Demo01 {
private volatile static int num = 0;
//不加volatile 一直 死循环
//加了 volatile 立马结束。 可见性。
public static void main(String[] args) {
new Thread(()->{
while(num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
2、不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰,也不能被分割。要么同时成功,要么同时失败
volatile
package com.junlin.tvolatile;
public class VDemo02 {
//volatile 不影响 最后结果 仍为18000左右
private volatile static int num = 0;
//可以在add 方面 加 synchronized num 为2 0000
private static void add(){
num ++;
}
public static void main(String[] args) {
//理论上 结果是 2 0000
for (int i = 1; i <= 20 ; i++) {
new Thread(()->{
for (int j = 1; j <=1000 ; j++) {
add(); //add 不加static 静态方法 无法调用
}
}).start();
}
//不加 while 礼让 num 15000 4000 浮动很大
//加了 礼让 num 为 18280 17641 16000以上。
while(Thread.activeCount() > 2){ //main gc 两个线程
Thread.yield();//礼让 一起跑 直到跑完
}
System.out.println(num);
}
}
简单的add 可以通过反编译方法。 先在 idea,生成的target文件,找到文件位置。直接在目录输入 cmd 。在当前位置打开cmd
private static void add(){
num ++; //不是一个原子性操作
}
通过 javap -c VDemo.class 反编译
C:\Users\junlin\IdeaProjects\lockTest\target\classes\com\junlin\tvolatile>javap -c VDemo02.class
如果 不加lock 和 synchronized ,怎么样保证原子性。
用了 lock 锁不住。。。无论是 add 方法 还是 main 方法。
使用原子类 解决原子性的问题
AtomicInteger 类。 结果也为 2 0000
num.getAndIncrement(); //AtomicInteger +1 方法 CAS
package com.junlin.tvolatile;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class VDemo02 {
//volatile 不影响 最后结果 仍为18000左右
private volatile static AtomicInteger num = new AtomicInteger(0);
//可以在add 方面 加 synchronized num 为2 0000
public static void add(){
num.getAndIncrement(); //不是一个原子性操作
}
public static void main(String[] args) {
//理论上 结果是 2 0000
for (int i = 1; i <= 20 ; i++) {
new Thread(()->{
for (int j = 1; j <=1000 ; j++) {
add(); //add 不加static 静态方法 无法调用
}
}).start();
}
//不加 while 礼让 num 15000 4000 浮动很大
//加了 礼让 num 为 18280 17641 16000以上。
while(Thread.activeCount() > 2){ //main gc 两个线程
Thread.yield();//礼让 一起跑 直到跑完
}
System.out.println(num);
}
}
这些类的底层都直接和操作系统挂钩!
在内存中修改值!
Unsafe是一个很特殊的存在!!!
3、指令重排
什么是指令重排 :
你写的程序,计算机并不是按照你写的那样去执行的。
源代码 --> 编译器优化的重排 --> 指令并行也可能重排 -->内存系统也会重排–> 执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性! HB原则。happen before
先前发生的事件 对 后面的事件具有可见性
非计算机专业
volatile可以避免指令重排:
内存屏障 也就是个CPU指令
作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性(利用这些特性,volatile 可先了可见性)
Volatile 是可以保持 可见性。
不能保证原子性,由于内存屏障,可以保证避免指令重排的现象的产生。
volatile 的内存屏障在 单例模式中 应用最多
饿汉式
DCL 懒汉式
18、彻底玩转单例模式
Double Check Lock DCL懒汉式 双重检测锁机制
1、饿汉式单例
空间多 换时间 少
(静态常量 后一点; 静态代码块。执行顺序优先。)
优点:
- 没有加任何的锁、执行效率比较高
- 用户体验比懒汉式好
- 使用的是ClassLoader ,里面代码是加了synchronized。 同步的问题,线程安全。
缺点:
全部加载,可能浪费内存。
没有使用这个实例。就会觉得浪费
package com.junlin.single;
//饿汉式单例
public class Hungry {
//单例 构造器私有
private Hungry(){
}
/**
* 2.类中创建对象----但是要在静态代码块中创建
* 这里要说一下final,作用是不允许被修改变量的值,能修改值的时机是静态代码块以及有参构造加载时。
* 这个参数 是内部构造出来的 hungry
*/
private final static Hungry hungry = new Hungry();
//给一个外部接口。获得单例
public static Hungry getInstance() {
return hungry;
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
getInstance();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
2、懒汉式单例
时间多(加锁,还有获取实例判断) 换 少空间
2.1 懒汉式 第一版
单线程安全,多线程是不安全的。
package com.junlin.single;
//懒汉式单例 第一版
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}
//初始化lazyMan 不赋值
private static LazyMan lazyMan;
public static LazyMan getInstance() {
if(lazyMan == null)
lazyMan = new LazyMan();
return lazyMan;
}
public static void main(String[] args) {
for (int i = 1; i <= 10 ; i++) {
new Thread(()->{
getInstance();
}).start();
}
}
}
2.2 懒汉式 第二版 (DCL 双重检测锁)懒汉式
package com.junlin.single;
//懒汉式单例 第一版
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}
//初始化lazyMan 不赋值
private static LazyMan lazyMan;
//双重检测锁机制 DCL
public static LazyMan getInstance() {
if(lazyMan == null){
//给class 加锁 比同步方法性能更高。已有对象,无需进入同步代码块。
synchronized (LazyMan.class) {
if (lazyMan == null)
lazyMan = new LazyMan();
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 1; i <= 10 ; i++) {
new Thread(()->{
getInstance();
}).start();
}
}
}
2.3 懒汉式 第三版 volatile 禁止指令重排
package com.junlin.single;
//懒汉式单例 第一版
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}
//初始化lazyMan 不赋值
private volatile static LazyMan lazyMan;
//双重检测锁机制 DCL
public static LazyMan getInstance() {
if(lazyMan == null){
//给class 加锁 比同步方法性能更高。已有对象,无需进入同步代码块。
synchronized (LazyMan.class) {
if (lazyMan == null)
lazyMan = new LazyMan();//不是一个原子性操作
/**
* 1. 分配内存空间
* 2. 执行构造方法,初始化对象
* 3. 把这个对象指向这个空间
*
* 123
* 132 A B 两个线程。 A完成,B还没完成,此时lazyman 还没有完成构造 就会造成干扰。需要加volatile
*/
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 1; i <= 10 ; i++) {
new Thread(()->{
getInstance();
}).start();
}
}
}
3、静态内部类 实现单例
优点:classloader机制,避免了线程不安全。同时也是利用静态内部类特点实现延迟加载,效率高。
外部类加载顺序:
1、外部类初次加载,会初始化静态变量、静态代码块、静态方法,但不会加载内部类和静态内部类。
2、实例化外部类,调用外部类的静态方法、静态变量,则外部类必须先进行加载,但只加载一次。
3、直接调用静态内部类时,外部类不会加载。
package com.junlin.single;
//静态内部类
public class Holder {
//静态内部类 比饿汉好。可以线程安全且懒加载。比懒汉好,可以懒加载,且线程安全
private Holder(){
}
public static class InnerClass{
private static final Holder holder = new Holder();
}
public static Holder getInstance(){
return InnerClass.holder;
}
}
4、用反射打败 (饿汉,懒汉,静态内部类)
问题:
1、已有对象,用反射创造第二个对象。
2、不用对象,直接反射创造两个对象。
3、使用反编译破解信号量加密
4.1、私有构造函数 加锁
package com.junlin.single;
import com.sun.imageio.plugins.common.LZWCompressor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//用反射打败 第一版
public class LazyMan {
private LazyMan() {
//私有构造函数 加锁,锁类。不为空报错,不允许继续构造
synchronized (LazyMan.class) {
if (lazyMan != null) {
throw new RuntimeException("不要试图使用反射破坏异常!!");
}
}
System.out.println(Thread.currentThread().getName() + "ok");
}
//初始化lazyMan 不赋值
private volatile static LazyMan lazyMan;
//双重检测锁机制 DCL
public static LazyMan getInstance() {
if (lazyMan == null) {
//给class 加锁 比同步方法性能更高。已有对象,无需进入同步代码块。
synchronized (LazyMan.class) {
if (lazyMan == null)
lazyMan = new LazyMan();
}
}
return lazyMan;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyMan newInstance = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
}
}
4.2、通过信号量的加密。阻止非破解的反射。
package com.junlin.single;
import com.sun.imageio.plugins.common.LZWCompressor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//反射破解单例 第二版
public class LazyMan {
//做一个信号量的标志
private static boolean junlin = false;
private LazyMan() {
//私有构造函数 加锁,锁类。不为空报错,不允许继续构造
synchronized (LazyMan.class) {
if (junlin==false) {
junlin =true;
}else{
throw new RuntimeException("不要试图使用反射破坏异常!!");
}
}
System.out.println(Thread.currentThread().getName() + "ok");
}
//初始化lazyMan 不赋值
private volatile static LazyMan lazyMan;
//双重检测锁机制 DCL
public static LazyMan getInstance() {
if (lazyMan == null) {
//给class 加锁 比同步方法性能更高。已有对象,无需进入同步代码块。
synchronized (LazyMan.class) {
if (lazyMan == null)
lazyMan = new LazyMan();//不是一个原子性操作
}
}
return lazyMan;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyMan newInstance = declaredConstructor.newInstance();
LazyMan newInstance2 = declaredConstructor.newInstance();
System.out.println(newInstance);
System.out.println(newInstance2);
}
}
5、使用枚举类实现单例
通过源码查看
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
使用反射破解。
报错。没有类似方法。
但是 idea 中 target 生成的enum方法 显示 无参构造。 idea 骗了我们 。
去源码分析:
C:\Users\junlin\IdeaProjects\lockTest\target\classes\com\junlin\single>javap -p EnumSingle.class
Compiled from "EnumSingle.java"
public final class com.junlin.single.EnumSingle extends java.lang.Enum<com.junlin.single.EnumSingle> {
public static final com.junlin.single.EnumSingle INSTANCE;
private static final com.junlin.single.EnumSingle[] $VALUES;
public static com.junlin.single.EnumSingle[] values();
public static com.junlin.single.EnumSingle valueOf(java.lang.String);
private com.junlin.single.EnumSingle();
public com.junlin.single.EnumSingle getInstance();
static {};
}
private com.junlin.single.EnumSingle(); 也是无参构造
javap 文件分解。反编译
通过javap -p 查看 显示所有类和成员。
下载 jad
使用命令 jad -sjava xx.class
5.1、枚举类 反编译源码
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
package com.junlin.single;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/junlin/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
发现。
private EnumSingle(String s, int i)
{
super(s, i);
}
有一个string,和一个int 构造。
package com.junlin.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance = EnumSingle.INSTANCE;
// EnumSingle instance2 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingle newInstance = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
}
}
Exception in thread "main" java.lang.NoSuchMethodException: com.junlin.single.EnumSingle.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.junlin.single.Test.main(EnumSingle.java:17)
Process finished with exit code 1
构造函数 放入 string,和int
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance = EnumSingle.INSTANCE;
// EnumSingle instance2 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle newInstance = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
}
}
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.junlin.single.Test.main(EnumSingle.java:19)
19、深入理解CAS
什么是CAS
底层原理,为了精深必须会!
package com.junlin.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
//CAS compareAndSwap 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2000);
//compareAndSet 底层就是 compareAndSwap
//public final boolean compareAndSet(int expect, int update)
//如果我期望达到了,那么更新,否则就不更新。CAS 是CPU的并发原语!
System.out.println(atomicInteger.compareAndSet(2000,2001));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2000,2001));
System.out.println(atomicInteger.get());
}
}
获取内存的偏移值
volatile 内存屏障。禁止指令重排。
且是一个自旋锁。
CAS :比较当前工作内存中的值和主内存的值,如果这个值是期望的,那么执行操作!如果不是就一直循环!
缺点:
1、循环会耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题
CAS : ABA问题 (狸猫换太子)
修改过有人捣乱
20、原子引用
乐观锁。 CAS
ABA问题
使用原子引用。
Interger var = ? 值是 -128-127
21、各种锁理解
1、公平锁、非公平所
公平锁:非常公平,不能插队,必须先来后遇到!
非公平所:非常不公平,可以插队(默认都是非公平)
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2、可重入锁
可重入锁(递归锁)
Synchronized 版
package com.junlin.lock;
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
//B需要等A解锁。所以输出结果是一对一对的
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");
}
}
Lock 版
3、自旋锁
SpinLock
自己写的自旋锁,利用原子引用类型。CAS原理。
锁,先锁的必须先解锁。不然一直自旋。
package com.junlin.lock;
import java.util.Timer;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 自旋锁 需要CAS 用原子引用来实现
* AtomicReference 是原子引用类型
*/
public class SpinLock {
//int 0
//Thread null 引用类型
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"====> myLock");
// 期望是null,不是null。就是false !false 就是true 。。 就一直自旋 直到。null
//!!!!!!!!!!!!很重要
while(!atomicReference.compareAndSet(null,thread)){
// System.out.println(Thread.currentThread().getName()+"还在自旋");
}
}
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"====> myUnLock");
//不用判断。直接更新。
atomicReference.compareAndSet(thread,null);
}
}
Test代码
package com.junlin.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
//底层使用的自旋锁CAS
SpinLock lock = new SpinLock();
/**
* T1线程 拿到锁,睡1--------------2--------------3秒 。T1 unlock
* T2睡一秒,也拿到锁。然后自旋。 T2自旋发现。
* (必须等T1的当前Thread引用,刚好满足期待然后unlock)
*/
new Thread(()->{
lock.myLock();//锁最好 放在 try catch finally 外面。
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();//锁最好 放在 try 外面。
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
},"T2").start();
}
}
输出结果:
T1 ====> myLock
T2 ====> myLock
T1 ====> myUnLock
T2 ====> myUnLock
4、死锁
死锁是什么?
死锁测试,怎么排除死锁。
解决问题
1、使用jps定位进程号
jps -l
查看活着的进程
2、使用 jstack 进程号找到死锁问题
面试工作中!排查问题:
1、日志
2、堆栈
jps(Java Virtual Machine Process Status Tool)是JDK 1.5提供bai的一du个显示当前zhi所有daojava进程pid的命令,简单实用,非zhuan常适合在linux/unix平台shu上简单察看当前java进程的一些简单情况
jstack(查看线程)、jmap(查看内存)和jstat(性能分析)
JVM性能调优监控工具
jps、 查看活着进程
jstack、 查看线程
jmap、jhat、 查看内存
jstat 性能分析
hread.currentThread();
System.out.println(Thread.currentThread().getName()+“====> myUnLock”);
//不用判断。直接更新。
atomicReference.compareAndSet(thread,null);
}
}
Test代码
```java
package com.junlin.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
//底层使用的自旋锁CAS
SpinLock lock = new SpinLock();
/**
* T1线程 拿到锁,睡1--------------2--------------3秒 。T1 unlock
* T2睡一秒,也拿到锁。然后自旋。 T2自旋发现。
* (必须等T1的当前Thread引用,刚好满足期待然后unlock)
*/
new Thread(()->{
lock.myLock();//锁最好 放在 try catch finally 外面。
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();//锁最好 放在 try 外面。
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
},"T2").start();
}
}
输出结果:
T1 ====> myLock
T2 ====> myLock
T1 ====> myUnLock
T2 ====> myUnLock
4、死锁
死锁是什么?
[外链图片转存中…(img-54qukR46-1663771093029)]
死锁测试,怎么排除死锁。
[外链图片转存中…(img-wMR5xOCc-1663771093029)]
解决问题
1、使用jps定位进程号
jps -l
查看活着的进程
2、使用 jstack 进程号找到死锁问题
[外链图片转存中…(img-P8k83Uro-1663771093030)]
面试工作中!排查问题:
1、日志
2、堆栈
jps(Java Virtual Machine Process Status Tool)是JDK 1.5提供bai的一du个显示当前zhi所有daojava进程pid的命令,简单实用,非zhuan常适合在linux/unix平台shu上简单察看当前java进程的一些简单情况
jstack(查看线程)、jmap(查看内存)和jstat(性能分析)
JVM性能调优监控工具
jps、 查看活着进程
jstack、 查看线程
jmap、jhat、 查看内存
jstat 性能分析
等使用详解