JUC并发
JUC 并发
普通业务的线程代码不方便Runnable 没有返回值、效率比可调concurrent相对低!
Java无法直接操作硬件
公平锁:不可以插队
非公平锁:可以插队
//原始非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//重载方法:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync(); // FairSync公平锁NonfairSync非
}
synchronized和lock的区别
① Synchronized 是 Java 内置关键字,Lock 是一个 Java 类
② Synchronied无法判断取锁的状态,Lock 可以判断
③ Synchronied 会自动释放锁,Lock 必须要手动加锁和手动释放锁!可能会遇到死锁
④ Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。
⑤ Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;
⑥ Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;
注: “广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。“
模拟生产者、消费者示例:
① Synchronized 是 Java 内置关键字,Lock 是一个 Java 类
② Synchronied无法判断取锁的状态,Lock 可以判断
③ Synchronied 会自动释放锁,Lock 必须要手动加锁和手动释放锁!可能会遇到死锁
④ Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。
⑤ Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;
⑥ Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;
广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。
代码示例
public static void main(String[] args) {
Data data = new Data();
// 创建一个生产者
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
// 创建一个消费者
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//这是一个缓冲类,生产和消费之间的仓库
class Data{
// 这是仓库的资源,生产者生产资源,消费者消费资源
private int num = 0;
// +1,利用关键字加锁
public synchronized void increment() throws InterruptedException {
// 首先查看仓库中的资源(num),如果资源不为0,就利用 wait 方法等待消费,释放锁
if(num!=0){
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
// 通知其他线程 +1 执行完毕
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
// 首先查看仓库中的资源(num),如果资源为0,就利用 wait 方法等待生产,释放锁
if(num==0){
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
// 通知其他线程 -1 执行完毕
this.notifyAll();
}
}
以上会存在虚假唤醒的问题!
就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
这也就是为什么用while而不用if的原因了,因为线程被唤醒后,执行开始的地方是wait之后
防止虚假唤醒
//这是一个缓冲类,生产和消费之间的仓库
class Data{
// 这是仓库的资源,生产者生产资源,消费者消费资源
private int num = 0;
// +1,利用关键字加锁
public synchronized void increment() throws InterruptedException {
// 首先查看仓库中的资源(num),如果资源不为0,就利用 wait 方法等待消费,释放锁
// 使用 if 存在虚假唤醒
while (num!=0){
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
// 通知其他线程 +1 执行完毕
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
// 首先查看仓库中的资源(num),如果资源为0,就利用 wait 方法等待生产,释放锁
while(num==0){
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
// 通知其他线程 -1 执行完毕
this.notifyAll();
}
}
八锁现象
八锁链接: link.
集合安全问题
CopyOnWrite //写入时复制、 COW 计算机程序设计领域的一种优化策略
CopyOnWriteArrayList比Vector强:
Vector的add方法加了synchronized 加了synchronized关键字效率就会降低
CopyOnWriteArraySet
Map安全的集合:ConcurrentHashMap
Callable:
注意的细节问题:
1、有缓存
2、可能会阻塞
为什么使用Callable:
不论是直接继承Thread,还是实现Runnable接口都需要调用Thread类中的start方法去向操作系统请求io,cup等资源。
但是run方法没有返回值如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
但是Callable中的Call方法带有返回值!
代码示例
package com.Kari.callabledemo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author Kari
* @date 2021年05月19日11:16 上午
*/
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);//适配类、因为Thread无法直接调用Callable
new Thread(futureTask,"线程").start();
new Thread(futureTask,"线程2").start();//结果会被缓存、效率高
String o = String.valueOf(futureTask.get());//可能会产生阻塞,所以应该放在最后或者使用异步通信操作
System.out.println(o);
}
}
class MyThread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("Good Job");
return "yeah";
}
}
常用的辅助类
CountDowmLatch:减法计数器
countDownLatch.countDown();//计数器减1
countDownLatch.await();//等待计数器归零才会执行后面的操作
代码示例
//等线程中的内容全部执行完才会执行 out
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);//总数为5
for (int i = 0; i <5 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"GG");
countDownLatch.countDown();//计数器减1
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归零才会执行后面的操作
System.out.println("out");
}
CyclicBarrier:加法计数器
代码示例
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(8,()->{
System.out.println("全部执行完成");
});
for (int i = 0; i <9 ; i++) {
final int I = i;//new thread是用lambda简化的一个类所以无法直接去到i 需要通过一个final常量
new Thread(()->{
System.out.println(Thread.currentThread().getName()+":::"+I);
try {
barrier.await();//等待加法计数器达到指定值:8
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
Smaphore:信号量
代码示例
/**
* semaphore.acquire(); 获得,如果满了则一直等待到被释放为止
* semaphore.release();释放; 会将当前到信号量释放加一,唤醒等待到线程
* 作用:
* 1、并发限流,控制最大的线程数
* 2、多个共享资源互斥到使用
*/
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(1);
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"进入停车位");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+"离开停车位");
semaphore.release();
}
},"线程1").start();
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"进入停车位");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+"离开停车位");
semaphore.release();
}
},"线程2").start();
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"进入停车位");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName()+"离开停车位");
semaphore.release();
}
},"线程3").start();
}
读写锁:更加细粒度读控制
独占锁(写锁)一次只能被一个线程占有
共享锁(读锁) 一次可被多个线程同时执行
读-读 共存
读-写 不可共存
写-写 不可共存
代码示例
public static void main(String[] args) {
MyCacheLock cacheLock = new MyCacheLock();
for (int i = 1; i < 8; i++) {
final int ii = i;
new Thread(() -> {
cacheLock.put(String.valueOf(ii), String.valueOf(ii + "sd"));
}, String.valueOf(ii)).start();
}
for (int i = 1; i < 8; i++) {
final int ii = i;
new Thread(() -> {
cacheLock.get(String.valueOf(ii));
}, String.valueOf(ii)).start();
}
}
}
class MyCacheLock {
private volatile Map<String, String> map = new HashMap<String, String>();
//读写锁 更加细粒度读控制
ReadWriteLock lock = new ReentrantReadWriteLock();
//写
public void put(String key, String value) {
lock.writeLock().lock();
try {
System.out.println("写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成!");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
//读
public String get(String key) {
lock.readLock().lock();
try {
System.out.println("读取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() +"读取完成!");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
return map.get(key);
}
}
阻塞队列
BLockingQueue是 Queue接口的实现类典型的阻塞队列
Queue是和Set、List同级的接口都继承了Collection
BLockingQueue的四组API:
方式 | 抛出异常 | 有返回值,不抛异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(, ,) |
移除 | remove() | poll () | take() | poll(,) |
检测队首元素 | element() | peek() | - | - |
代码示例展示:
//抛出异常
public static void test1() {
//创建并设置队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("c"));
//出现异常:Queue full 队列满了
// System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.element());
//异常:NoSuchElementException
// System.out.println(blockingQueue.remove());
}
//有返回值,不抛出异常
public static void test2() {
//创建并设置队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("c"));
//返回false 不出现异常
// System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//返回null 不出现异常
// System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.peek());
}
//阻塞等待
public static void test3() throws InterruptedException {
//创建并设置队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d");//死等
System.out.println(blockingQueue);
blockingQueue.take();
blockingQueue.take();
blockingQueue.take();
// blockingQueue.take();//死等
System.out.println(blockingQueue);
}
//超时等待
public static void test4() throws InterruptedException {
//创建并设置队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
blockingQueue.offer("d",3, TimeUnit.SECONDS);//等待3秒
System.out.println(blockingQueue);
blockingQueue.poll();
blockingQueue.poll();
blockingQueue.poll();
blockingQueue.poll(3,TimeUnit.SECONDS);//等待3秒
System.out.println(blockingQueue);
}
SynchronousQueue(同步队列)
没有容量、不存储元素,put一个元素必须take取出来否则不能继续put
代码示例:
public static void main(String[] args) {
SynchronousQueue<String> queue = new SynchronousQueue<String>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"==>"+"put a");
queue.put("a");
System.out.println(Thread.currentThread().getName()+"==>"+"put b");
queue.put("b");
System.out.println(Thread.currentThread().getName()+"==>"+"put c");
queue.put("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Put").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"==>"+queue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"==>"+queue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"==>"+queue.take());
} catch ( InterruptedException e ) {
e . 打印堆栈跟踪();} } , "取" ) 。开始( ) ; } /* 输出结果:Put==>put a Take==>a Put==>put b Take==>b Put==>put c Take==>c */
线程池
三大方法、七大参数、四种拒绝策略
程序的运行本质:占用系统资源、优化资源使用 =》池化技术
线程池、连接池、内存池、对象池; 创建和销毁十分的浪费资源
池化技术:事先准备好一些资源,有人用到就来拿,用完之后还回来
线程池的好处:
1、降低资源消耗
2、提高响应速度
3、方便管理
线程复用、可以控制最大并发数、管理线程
线程三大方法:
代码示例:
/**
* @author Kari
* @date 2021年05月21日3:33 下午
*/
//Executors 工具类
public class demo {
public static void main(String[] args) {
ExecutorService executorService=null;
/**
* 下面代码输出:
* pool-1-thread-1ok
* pool-1-thread-1ok
* pool-1-thread-1ok
* pool-1-thread-1ok
* pool-1-thread-1ok
*/
executorService = Executors.newSingleThreadExecutor();//单个线程
/**
*pool-2-thread-2ok
* pool-2-thread-3ok
* pool-2-thread-1ok
* pool-2-thread-2ok
* pool-2-thread-3ok
*/
executorService = Executors.newFixedThreadPool(3);//创建一个固定的线程池大小
/**
* pool-3-thread-1ok
* pool-3-thread-5ok
* pool-3-thread-4ok
* pool-3-thread-3ok
* pool-3-thread-2ok
*/
executorService = Executors.newCachedThreadPool();//可伸缩的
try{
for (int i = 0; i <5 ; i++) {
executorService.execute(()->{
//使用线程池之后就用线程池来创建线程
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}catch (Exception e){
}finally {
executorService.shutdown();//关闭线程池
}
}
}
四种拒绝策略
new ThreadPoolExecutor.AbortPolicy());//银行满了还有人来,不处理这个人并且抛出异常
new ThreadPoolExecutor.CallerRunsPolicy());//哪里来的去哪里(会通过main线程去执行)
new ThreadPoolExecutor.DiscardPolicy());//队列满了,不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy());//队列满了,尝试去替换最老的 也不会抛出异常
代码示例:
public static void main(String[] args) {
//自定义线程池
ExecutorService service = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
// new ThreadPoolExecutor.AbortPolicy());//银行满了还有人来,不处理这个人并且抛出异常
// new ThreadPoolExecutor.CallerRunsPolicy());//哪里来的去哪里(会通过main线程去执行)
// new ThreadPoolExecutor.DiscardPolicy());//队列满了,不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy());//队列满了,尝试去替换最老的 也不会抛出异常
try {
for (int i = 0; i < 100; i++) {
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + "jjj");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
service.shutdown();
}
}
ThreadPoolExecutor各个参数解析:
重点问题:最大线程数如何定义
1、CPU密集型:几核的电脑就设置为几,实现保存CPU最高效率运行
获取cpu核数
System.out.println(Runtime.getRuntime().availableProcessors());
2、IO密集型:判断程序中十分消耗io的线程,需要大于这个数量(最好设置为线程数量的两倍)
1)corePoolSize:核心线程数
核心线程会一直存活,及时没有任务需要执行
当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
2)queueCapacity:任务队列容量(阻塞队列)
当核心线程数达到最大时,新任务会放在队列中排队等待执行
3)maxPoolSize:最大线程数
当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
4)keepAliveTime:线程空闲时间
当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
如果allowCoreThreadTimeout=true,则会直到线程数量=0
5)allowCoreThreadTimeout:允许核心线程超时
6)rejectedExecutionHandler:任务拒绝处理器
四大函数式接口
//Function 函数型接口 有一个输人参数和一个出
@Test
public void T0() {
/**
*
*/
// Function function = new Function() {
// @Override
// public Object apply(Object o) {
// return o;
// }
// };
Function function = a->{
return a;
};
System.out.println(function.apply(299));//299
}
//Predicate 函数式接口 有一个输入参数和一个布尔类型的返回值
@Test
public void T1(){
Predicate<String> predicate = sc->{return sc.isEmpty();};
System.out.println(predicate.test(" "));//false
}
//Consumer 消费型接口,只有输入参数没有返回值。
@Test
public void T2(){
Consumer<String> consumer = sq->{
System.out.println(sq);// ap
};
consumer.accept("ap");
}
//Supplier 供给型接口,只有返回值没有输入参数
@Test
public void T3(){
Supplier<String> supplier =() ->{
return "I'm loser";
};
System.out.println(supplier.get()); //I'm loser
}
Stream流式计算
大数据:存储+计算
例如Mysql、集合这些都是用来存储东西的
而计算都应该交给流来做
public static void main(String[] args) {
User u1 = new User(1, "a",16);
User u2 = new User(2, "b",25);
User u3 = new User(3, "c",67);
User u4 = new User(4, "d",29);
//创建集合
List<User> list = Arrays.asList(u1, u2, u3, u4);
//转化成流
list.stream()
.filter(u -> {
return u.getId() % 2 == 0;
})
.filter(u->{//filter 有参、返回值为布尔类型
return u.getAge()>23;
})
.map(u->{//map映射
return u.getName().toUpperCase();
})
.sorted((us1,us2)->{//sorted 排序
return us2.compareTo(us1);//compareTo方法是比较并排序
})
.limit(1)//用于分页 当前使用目的是只输出一个用户名
.forEach(System.out::println);
}
/**
* 定义实体类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
class User {
private int id;
private String name;
private int age;
}
ForkJoin
关键字:并行执行任务、提高效率、提高数据量
本质:把大任务拆分成一个个都小任务
特点:工作窃取(当a、b两个线程同时执行任务 b率先完成任务后会窃取a未完成的任务 帮a执行完成)
代码示例:
//普通方法
@org.junit.Test
public void test1() {
Long sum = 0L;
Long sta = System.currentTimeMillis();
for (int i = 1; i <= 10_0000_0000; i++) {
sum += i;
}
Long sto = System.currentTimeMillis();
System.out.println(sum + "时间:" + (sto - sta));
}
//使用ForkJoin
@org.junit.Test
public void test2() throws ExecutionException, InterruptedException {
Long sta = System.currentTimeMillis();
ForkJoinPool forkJoinPoll = new ForkJoinPool();
ForkJoinTask<Long> demo = new demo(0L, 10_0000_0000L);
求和结果方法:
1、
forkJoinPoll.execute(demo);//执行任务 没有返回值
System.out.println(demo.get());//获取值
2、
ForkJoinTask<Long> submit = forkJoinPoll.submit(demo);//提交任务 有返回值
Long sum = submit.get();
Long sto = System.currentTimeMillis();
System.out.println(sum + "时间:" + (sto - sta));
}
//并行流
@org.junit.Test
public void test3(){
Long sta = System.currentTimeMillis();
//Stream流
Long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);//parallel并行计算 reduce求和
Long sto = System.currentTimeMillis();
System.out.println(sum + "时间:" + (sto - sta));
}
_____________________ForkJoin自定义_________________
public class demo extends RecursiveTask<Long> {
private Long sta;
private Long sto;
private Long len = 10000L;
public demo(Long sta, Long sto) {
this.sta = sta;//1
this.sto = sto;//20000L
}
//计算方法
@Override
protected Long compute() {
if ((sto - sta) < len) {
Long sum = 0L;
for (Long i = sta; i <= sto; i++) {
sum += i;
}
return sum;
} else {
Long mm = (sto + sta) / 2;//获取中间值
demo demo = new demo(sta, mm);
demo.fork();//拆分任务把任务压入线程队列
demo demo1 = new demo(mm + 1, sto);
demo1.fork();
return demo.join()+demo1.join();
}
}
}
JMM(内存模型)
并不存在相当于一种约定
同步约定:
1、线程解锁前: 必须把共享变量立刻刷回主存
2、线程加锁前: 必须读取主存中的最新值到工作内存中
3、加锁、解锁是同一把锁
八种操作:
Volatile
1、保证可见性
2、不保证原子性
3、禁止指令重排
处理器在执行指令重排会考虑数据之间的依赖性问题:
不会造成影响示例:
int x=1;//1
int y=2;//2
x=x+2;//3
y=x*y;//4
我们预期执行是1234,发送指令重排后可能是 2134、1324
但不会出现 4123 这是数据之间的依赖性导致的
造成影响的示例:
abcd四个值默认为0
线程A: || 线程B:
d=b; || c=a;
a=3; || b=1;
预期值:
d=0;c=0
发生指令重排可能会先执行a=3或者b=1
导致结果变成:
d=1;c=3;
这样就会对最后的结果造成影响
Volatile避免指令重排:
内存屏障、CPU指令。作用:
1、保证特定的操作的执行顺序
2、保证某些变量的内存可见性(利用这些特性就可以实现可见性)
Voliate的内存屏障在单例模式中使用最多!
单例模式
饿汉式
public class Hungry {
/**
* 很有可能会浪费空间
*/
byte [] bytes1 = new byte[1024*1024];
byte [] bytes2 = new byte[1024*1024];
byte [] bytes3 = new byte[1024*1024];
byte [] bytes4 = new byte[1024*1024];
//私有构造器,别人无法new 保证只有一个对象
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
DCL懒汉式
//懒汉式
public class LanHan {
private static boolean tmp = false;
private LanHan() {
System.out.println(Thread.currentThread().getName());
synchronized (LanHan.class) {
if (tmp) {
tmp = true;
} else {
throw new RuntimeException("拒绝!");
}
}
}
//不加volatile会存在指令重排风险
private volatile static LanHan LANHAN;
//双重检测锁模式 懒汉式单利 DCL懒汉式
private static LanHan getInstance() {
if (LANHAN == null) {
synchronized (LanHan.class) {
if (LANHAN == null) {
LANHAN = new LanHan();//不是原子性操作,存在发生指令重排的可能
/**
* 如果不加volatile:
* new的这个过程:
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
*我们预期的执行顺序:123
*
* 比如现在A线程执行顺序是132
* 之后B线程进来之后此时的LANHAN还没有完成构造
*/
}
}
}
return LANHAN;
}
________________反射可以破坏懒汉式单例模式____
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//反射会破坏单利模式
Field tmp = LanHan.class.getDeclaredField("tmp");
tmp.setAccessible(true);
Constructor<LanHan> declaredConstructor = LanHan.class.getDeclaredConstructor(null);//空参 构造器
declaredConstructor.setAccessible(true);//无视私有构造器
LanHan l1 = declaredConstructor.newInstance();
tmp.set(l1,false);
LanHan l2 = declaredConstructor.newInstance();
System.out.println(l1 + "\n" + l2);
}
}
反射无法破坏枚举单例模式
public enum Enumdemo {
INC;
private Enumdemo getInc(){
return INC;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Enumdemo enumdemo = Enumdemo.INC;
Constructor<Enumdemo> declaredConstructor = Enumdemo.class.getDeclaredConstructor(null);//无参构造器
declaredConstructor.setAccessible(true);//无视私有构造
Enumdemo enumdemo1 = declaredConstructor.newInstance();
System.out.println(enumdemo+"\n"+enumdemo1);
}
}
深入了解CAS
public class casDemo {
//CAS : compareAndSet 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//boolean compareAndSet(int expect, int update)
//期望值、更新值
//如果实际值 和 我的期望值相同,那么就更新
//如果实际值 和 我的期望值不同,那么就不更新
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
//因为期望值是2020 实际值却变成了2021 所以会修改失败
//CAS 是CPU的并发原语
atomicInteger.getAndIncrement(); //++操作
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
缺点:
循环会耗时;
一次性只能保证一个共享变量的原子性;
它会存在ABA问题
CAS的ABA
ABA产生代码示例:
AtomicInteger atomicInteger = new AtomicInteger(2020);
new Thread(()->{
System.out.println(atomicInteger.compareAndSet(2020, 10000));
System.out.println(atomicInteger.get());
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(atomicInteger.compareAndSet(10000, 2020));
System.out.println(atomicInteger.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(atomicInteger.compareAndSet(2020, 2023));
System.out.println(atomicInteger.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
ABA解决代码示例:
public void abajiejue(){
/**
* 解决ABA问题 加上版本号 这样可以知道有人修改过数据
* 注意此时参数都是Integer类型的 这个类的取值范围如果不是-128到127之间,就会是不同的对象!!!
* 而此时比较的刚好是对象的内存地址!所以不在这个范围内会出现false
*/
//比AtomicInteger多了个版本号的参数 类似乐观锁
AtomicStampedReference<Integer> reference = new AtomicStampedReference(10,1);
new Thread(()->{
int ver = reference.getStamp();//获取版本号
System.out.println("版本号ver"+ver);
//期望值,更新值,当前版本号,版本号加一
System.out.println(reference.compareAndSet(10,
100,
reference.getStamp(),
reference.getStamp()+1));
System.out.println(reference.getStamp());
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
int ver = reference.getStamp();//获取版本号
System.out.println("版本号ver:"+ver);
//期望值,更新值,当前版本号,版本号加一
System.out.println(reference.compareAndSet(100, 10,reference.getStamp(),reference.getStamp()+1));
System.out.println(reference.getStamp());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(4);
int ver = reference.getStamp();//获取版本号
System.out.println("版本号ver"+ver);
//期望值,更新值,当前版本号,版本号加一
System.out.println(reference.compareAndSet(10, 120,reference.getStamp(),reference.getStamp()+1));
System.out.println(reference.getStamp());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
可重入锁
(套娃锁)
重入就是说某个主题已经获得而获得某个锁,可以再次获得死锁,可以多次获得相同的锁
synchronized示例:
public class SynchronizedDemo {
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");
}
}
Lock锁:
public class LockDemo {
public static void main(String[] args) {
Phone2 phone2 = new Phone2();
new Thread(()->{
phone2.sms();
}).start();
new Thread(()->{
phone2.sms();
}).start();
}
}
class Phone2{
Lock lock = new ReentrantLock();
public void sms(){
lock.lock();//细节:这个两把锁,两个钥匙
// lock 锁必须配对,否则就是死锁在里面
try {
System.out.println(Thread.currentThread().getName()+"=>sms");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"=>call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
自旋锁
代码示例:
(设计自己的自旋锁)
//自旋锁
public class KariLock {
AtomicReference<Thread> reference = new AtomicReference();
public void mylock(){
System.out.println(Thread.currentThread().getName()+"进入锁");
while (!reference.compareAndSet(null,Thread.currentThread())){
}
}
public void myUnlock(){
System.out.println("\n"+Thread.currentThread().getName()+"解锁");
reference.compareAndSet(Thread.currentThread(),null);
}
}
public class TestLock {
public static void main(String[] args) {
KariLock lock = new KariLock();
new Thread(()->{
lock.mylock();
try {
TimeUnit.SECONDS.sleep(3);//模拟业务
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnlock();
}
}).start();
new Thread(()->{
lock.mylock();
try {
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnlock();
}
}).start();
}
}
结论:线程0拿到锁后休眠,线程1会持续自旋,直到线程0解锁后,线程1才能解锁
死锁排查:
使用 jps 定位进程号,jdk 目录 bin 下 :有一个 jps
在IDea中的Terminal中输入 jsp-l
使用 jstack
进程进程号找到死锁信息
jstack 9090
在工作中排查问题除了日志以外还可以查看堆栈信息