其实就是java.util.concurrent java.util.concurrent.locks 这些包
业务:普通的线程 Thread
Runnable 没有返回值 效率比callable低 实际开发中都是用callable
java 默认有2个线程 一个main 主线程 一个GC 回收线程
Java 不能开启线程 本地方法调用,底层是C++ ,Java 无法操作硬件
并发(多个线程操作同一个资源)
当CPU只有一核,模拟出来多条线程,快速交替
并行(多个人一起行走)
CPU多核,多个线程可以同时执行
//获取CPU的核数
Runtime.getRuntime().availableProcessors
并发编程的本质:充分利用CPU的资源
//新生
NEW
//运行
RUNNABLE
//阻塞
BLOCKED
//等待,死死的等待
WAITING
//超时等待
TIMED_WAITING
//终止
TERMINATED
1.来自不同的类
wait来自Object类
sleep来自Thread类
2.关于锁的释放
wait会释放锁,sleep不会释放锁
3.使用范围
wait必须在同步代码块中才能使用
sleep 可以随便在那里都可以使用
可重进入锁 读锁 写锁
怎么实现的
l.lock(); // 加锁 l.unlock // 解锁
NonfairSync() // 非公平锁
不公平就是因为可以插队(默认就是非公平锁)
fairSync(); //公平锁
就是先来后到
用while循环而不是用if判断 防止虚假唤醒
Condition精准的通知和唤醒线程
condition 同步监视器
如何判断锁的是谁!永远的知道什么锁,锁到底锁的是谁
synchronized锁的对象是调用者
在synchronized前面加了static锁的是class类模板
List不安全
/**
java.util.ConcurrentModificationException //并发写入错误
* ArrayList 之线程不安全
* 解决办法
* 1.List arrayList = new Vector<>();
vector的add有 synchronized
* 2.List arrayList = Collections.synchronizedList(new ArrayList<>());
* 3.List arrayList = new CopyOnWriteArrayList<>();
JUC解决 写入时复制的意思
*/
List<String> arrayList = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
arrayList.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(arrayList);
},String.valueOf(i)).start();
}
set不安全
// Set
// Set
Set<Object> set = new CopyOnWriteArraySet();
for (int i = 0; i < 100; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
hashSet 底层是什么
add 就是hashMap的K key 是不能重复的
Map不安全
//map是这样用的嘛? 不是工作中不用HashMap
//默认等价于什么? new HasMap<>(16,0.75);
//Map map= new HashMap<>();
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
研究ConcurrentHashMap的原理
ConcurrentHashMap 是线程安全且高效的HashMap。
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//实现Callable 不能直接用 new Thread().start执行 需要找一个中间的是适配器
// 那么就需要Runnable 的实现类FutureTask
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread); //适配类的用法
new Thread(futureTask).start();
Object o = futureTask.get();//这个是获取返回值 有可能会阻塞要把它放到最后
System.out.println(o);
}
}
class MyThread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("进入call方法");
return "打印的方法";
}
}
总结
FutureTask futureTask = new FutureTask(实现了Callable的类); //适配类的用法
有缓存
结果可能需要等待 会阻塞
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"出门");
countDownLatch.countDown(); //数量-1操作
},String.valueOf(i)).start();
}
// countDownLatch.await(); 等待计数器归零的意思 就是等上面的全部执行完毕 在执行下面的操作
countDownLatch.await();
System.out.println("------------------------关门-----------------");
}
}
原理
CountDownLatch countDownLatch = new CountDownLatch(6); // 多个数量
countDownLatch.countDown(); 数量-1操作
countDownLatch.await(); 等待计数器归零 才能往下面执行
每次有线程调用counDown()数量-1,假设计数器变为0,CountDownLatch.await()就会被唤醒,继续执行
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙");
});
for (int i = 0; i <=7; i++) {
int finalI = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集了第"+ finalI+"龙珠");
try {
cyclicBarrier.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},"唐鸿").start();
}
}
}
信号量
public class SemaphoreDem {
public static void main(String[] args) {
// 线程 数量: 停车位 限流的作用
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <=6; i++) {
new Thread(()->{
try {
semaphore.acquire(); //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(); //释放
}
},String.valueOf(i)).start();
}
}
}
原理
semaphore.acquire(); 获得,假设如果已经满了,等待被释放位置 -1的操作
semaphore.release(); 释放,会将当前的信号量释放+1,然后唤醒等待的线程
作用:多个共享资源互斥的使用 ,并发限流,控制最大的线程数
**
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
* 读-读 可以共存
* 读-写 不可以共存
* 写-写 不可以共存
*/
public class RendWritLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 10; i++) {
int temp = i;
new Thread(()->{
myCache.put(temp +"", temp +"");
},String.valueOf(i)).start();
}
for (int i = 0; i < 10; i++) {
int temp = i;
new Thread(()->{
myCache.get(temp +"");
},String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile Map<String,String> map =new HashMap<>();
//synchronized 和Lock 也能完成操作 但是用写锁 是因为更加细粒度的控制
//写锁:
ReentrantReadWriteLock readWriteLock= new ReentrantReadWriteLock();
public void put(String k,String v){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+k);
map.put(k,v);
System.out.println(Thread.currentThread().getName()+"写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
//读锁
public void get(String k){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+k);
String put = map.get(k);
System.out.println(Thread.currentThread().getName()+"读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
学会使用队列
添加和移除
方式 | 抛出异常 | 有返回值值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(“d”, 2, TimeUnit.SECONDS) |
移除 | remove | pooll | take | pooll(2, TimeUnit.SECONDS) |
检测队首元素 | element | peek |
/**
*抛出异常
*/
public static void test(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3); //capacity 容量
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
// System.out.println(arrayBlockingQueue.add("c")); //java.lang.IllegalStateException: Queue full 抛出异常说队列已经满了
System.out.println( arrayBlockingQueue.element()); //产看队列首位元素
System.out.println("______________________________");
System.out.println( arrayBlockingQueue.remove());
System.out.println( arrayBlockingQueue.remove());
System.out.println( arrayBlockingQueue.remove());
// System.out.println( arrayBlockingQueue.remove()); java.util.NoSuchElementException
}
public static void test2(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3); //capacity 容量
System.out.println( arrayBlockingQueue.offer("a"));
System.out.println( arrayBlockingQueue.offer("b"));
System.out.println( arrayBlockingQueue.offer("c"));
// System.out.println( arrayBlockingQueue.offer("c")); 多加数据不会抛出异常 只会返回一个false
System.out.println(arrayBlockingQueue.poll());
System.out.println("----------------------------");
System.out.println(arrayBlockingQueue.peek()); //peek 检测首元素
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
// System.out.println(arrayBlockingQueue.poll()); 取出多的数据不会抛出异常 会返回一个null
}
public static void test3() throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3); //capacity 容量
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
// arrayBlockingQueue.put("d"); //队列没有位置了 在等待位置空出 所以一直阻塞
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
// System.out.println(arrayBlockingQueue.take()); //没有这个元素,一直在阻塞
}
public static void test4() throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3); //capacity 容量
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("c"));
System.out.println(arrayBlockingQueue.offer("b"));
// System.out.println(arrayBlockingQueue.offer("d", 2, TimeUnit.SECONDS)); //超时等待 超过时间就会返回false
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
// System.out.println(arrayBlockingQueue.poll( 2, TimeUnit.SECONDS)); // 超时等待 超过时间就会返回null
}
/**
* SynchronousQueue 同步队列
* 和其他的BlockingQueue不一样,SynchronousQueue 不存储元素
* put了一个元素,必须从里面先take取出来,否则不能在put进去值
*/
public class synchronizedQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> objectSynchronizedQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"塞入a");
objectSynchronizedQueue.put("a");
System.out.println(Thread.currentThread().getName()+"塞入b");
objectSynchronizedQueue.put("b");
System.out.println(Thread.currentThread().getName()+"塞入c");
objectSynchronizedQueue.put("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T").start();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"取出"+objectSynchronizedQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"取出"+objectSynchronizedQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"取出"+objectSynchronizedQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
池化技术
降低资源的消耗
提高响应的速度
方便管理
线程复用,可以控制最大的并发数,管理线程
阿里巴巴 开发手册规定线程池的创建
/**
* Executors 工具类 3大创建线程池的方法
*/
public class Demo {
public static void main(String[] args) {
ExecutorService threadpool = Executors.newSingleThreadExecutor(); //单个线程
// ExecutorService threadpool = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
// ExecutorService threadpool = Executors.newCachedThreadPool();// 可伸缩的。遇强则强,预弱则弱
for (int i = 0; i < 10; i++) {
//使用了线程池之后,使用线程池来创建线程
threadpool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
//线程池用完一定要关闭线程
threadpool.shutdown();
}
}
源码分析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
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, // 最大21亿
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
/**
* new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常
*/
public class pool {
public static void main(String[] args) {
//自定义创建线程池 工作中只用ThreadPoolExecutor
ExecutorService threadPool = new ThreadPoolExecutor(2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
//最大承载:Deque + max
for (int i = 1; i <= 15; i++) {
//使用了线程池之后,就用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完就的 关闭线程池
threadPool.shutdown();
}
}
}
池的最大的数值该如何去设置
了解IO密集型 和CPU密集型(调优)
cpu密集型,几核,就是几,可以保持CPU的效率最高
获取CPU的核数 :Runtime.getRuntime().availableProcessors
Io 密集型 > 判断你程序中十分耗IO的线程
程序 15个大型任务 IO十分占用资源
函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// 泛型、枚举、反射
// lambda表达式、链式编程、函数式接口、Stream流式计算
// 超级多FunctionalInterface
// 简化编程模型,在新版本的框架底层大量应用!
// foreach(消费者类的函数式接口)
@FunctionalInterface 意思的就是函数式接口
简化编程模型,在新版本的框架底层大量应用
Function 函数式接口
package com.haust.function;
import java.util.function.Function;
/**
* Function 函数型接口, 有一个输入参数,有一个输出参数
* 只要是 函数型接口 可以 用 lambda表达式简化
*/
public class Demo01 {
public static void main(String[] args) {
/*Function function = new
Function() {
@Override
public String apply(String str) {
return str;
}
};*/
// lambda 表达式简化:
Function<String,String> function = str->{
return str;};
System.out.println(function.apply("asd"));
}
}
Predicate 断定型接口 断定型接口:有一个输入参数,返回值只能是 布尔值!
package com.haust.function;
import java.util.function.Predicate;
/*
* 断定型接口:有一个输入参数,返回值只能是 布尔值!
*/
public class Demo02 {
public static void main(String[] args) {
// 判断字符串是否为空
/*Predicate predicate = new Predicate(){
@Override
public boolean test(String str) {
return str.isEmpty();//true或false
}
};*/
Predicate<String> predicate =
(str)->{
return str.isEmpty(); };
System.out.println(predicate.test(""));//true
}
}
Consumer 消费型接口
package com.haust.function;
import java.util.function.Consumer;
/**
* Consumer 消费型接口: 只有输入,没有返回值
*/
public class Demo03 {
public static void main(String[] args) {
/*Consumer consumer = new Consumer() {
@Override
public void accept(String str) {
System.out.println(str);
}
};*/
Consumer<String> consumer =
(str)->{
System.out.println(str);};
consumer.accept("sdadasd");
}
}
Supplier 供给型接口
package com.haust.function;
import java.util.function.Supplier;
/**
* Supplier 供给型接口 没有参数,只有返回值
*/
public class Demo04 {
public static void main(String[] args) {
/*Supplier supplier = new Supplier() {
@Override
public Integer get() {
System.out.println("get()");
return 1024;
}
};*/
Supplier supplier = ()->{
return 1024; };
System.out.println(supplier.get());
}
}
其它详细内容可以查看该网址
https://www.cnblogs.com/Garnett-Boy/p/11151459.html
什么是Stream流式计算
大数据:存储 + 计算
集合、MySQL 本质就是存储东西的;
计算都应该交给流来操作!
/**
* 题目要求:
* 现在有5个用户 筛选:
* 1.ID必须是偶数
* 2.年龄必须大于23岁
* 3.用户名转为大写字母
* 4,用户名写字母倒着排序
* 5.只输出一个用户
*/
public class Test {
public static void main(String[] args) {
User a1 = new User(1, "a", 21);
User a2 = new User(2, "b", 22);
User a3 = new User(3, "c", 23);
User a4 = new User(4, "d", 24);
User a5 = new User(6, "e", 25);
//集合都是存数据
List<User> list = Arrays.asList(a1, a2, a3, a4, a5);
//流用来计算
list.stream()
.filter(a->{
return a.getId()%2==0; }) ///filter 通过里面的判断过滤List的内容
.filter(a->{
return a.getAge()>23;})
.map(a->{
return a.getName().toUpperCase();}) //map 键值对的映射 toUpperCase()改变大小写
.sorted( (u1,u2)->{
return u2.compareTo(u1);}) // sorted 两个值的排序比较
.limit(1)
.forEach(System.out::println);
}
}
什么是 ForkJoin
ForkJoin在JDK1.7 并行执行任务!提高效率。大数据
大数据:Map Reduce (把大任务拆分为小任务)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OfwuJt7B-1621039866134)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210407201144414.png)]
ForkJoin 的特点 :工作窃取
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LsfTavCw-1621039866134)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210407201151307.png)]
package com.haust.forkjoin;
import java.util.concurrent.RecursiveTask;
/**
* 求和计算的任务!
* 3000 6000(ForkJoin) 9000(Stream并行流)
* // 如何使用 forkjoin
* // 1、forkjoinPool 通过它来执行
* // 2、计算任务 forkjoinPool.execute(ForkJoinTask task)
* // 3. 计算类要继承 RecursiveTask(递归任务,有返回值的)
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start; // 1
private Long end; // 1990900000
// 临界值
private Long temp = 10000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
// 计算方法
@Override
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();
}
}
}
测试代码:
package com.haust.forkjoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/**
* 同一个任务,别人效率高你几十倍!
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1(); // 12224
// test2(); // 10038
// test3(); // 153
}
// 普通程序员
public static void test1(){
Long sum = 0L;
long start = System.currentTimeMillis();
for (Long i = 1L; i <= 10_0000_0000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(end-start));
}
// 会使用ForkJoin
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));
}
public static void test3(){
long start = System.currentTimeMillis();
// Stream并行流 () (]
long sum = LongStream
.rangeClosed(0L, 10_0000_0000L) // 计算范围(,]
.parallel() // 并行计算
.reduce(0, Long::sum); // 输出结果
long end = System.currentTimeMillis();
System.out.println("sum="+"时间:"+(end-start));
}
}
Future 设计的初衷: 对将来的某个事件的结果进行建模
package com.haust.future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* 异步调用: CompletableFuture
* 异步执行
* 成功回调
* 失败回调
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 没有返回值的 runAsync 异步回调
// CompletableFuture completableFuture =
// CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(
// Thread.currentThread().getName()+"runAsync=>Void");
// });
//
// System.out.println("1111");
//
// completableFuture.get(); // 获取阻塞执行结果
// 有返回值的 supplyAsync 异步回调
// ajax,成功和失败的回调
// 返回的是错误信息;
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
*/
}
}
请你谈谈你对 Volatile 的理解
Volatile 是 Java 虚拟机提供轻量级的同步机制,类似于synchronized 但是没有其强大。
1、保证可见性
2、不保证原子性
3、防止指令重排
什么是JMM
JMM : Java内存模型,不存在的东西,概念!约定!
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存。
2、线程加锁前,必须读取主存中的最新值到工作内存中!
3、加锁和解锁是同一把锁。
线程 工作内存 、主内存
8 种操作:
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和writ操作在某些平台上允许例外)
JMM 对这八种指令的使用,制定了如下规则:
问题: 程序不知道主内存的值已经被修改过了
保证可见性
package com.haust.tvolatile;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
// 不加 volatile 程序就会死循环!
// 加 volatile 可以保证可见性
private volatile static int num = 0;
public static void main(String[] args) {
// main
new Thread(()->{
// 线程 1 对主内存的变化不知道的
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败
java.util.concurrent.atomic 多线程下保证原子性的包
import java.util.concurrent.atomic.AtomicInteger;
package com.haust.tvolatile;
import java.util.concurrent.atomic.AtomicInteger;
// volatile 不保证原子性
public class VDemo02 {
// volatile 不保证原子性
// 原子类的 Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
// num++; // 不是一个原子性操作
num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
}
public static void main(String[] args) {
//理论上num结果应该为 2 万
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000 ; j++) {
add();
}
}).start();
}
// 判断只要剩下的线程不大于2个,就说明20个创建的线程已经执行结束
while (Thread.activeCount()>2){
// Java 默认有 main gc 2个线程
Thread.yield();
}
System.out.println(Thread.currentThread().getName()
+ " " + num);
}
}
如果不加 lock 和 synchronized ,怎么样保证原子性
使用原子类,解决原子性问题。
// volatile 不保证原子性
// 原子类的 Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
// num++; // 不是一个原子性操作
num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
}
这些类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!
指令重排
什么是指令重排?:我们写的程序,计算机并不是按照你写的那样去执行的。
源代码 —> 编译器优化的重排 —> 指令并行也可能会重排 —> 内存系统也会重排 ——> 执行
处理器在执行指令重排的时候,会考虑:数据之间的依赖性
int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4
我们所期望的:1234 但是可能执行的时候会变成 2134 或者 1324
但是不可能是 4123!
前提:a b x y 这四个值默认都是 0:
可能造成影响得到不同的结果:
线程A | 线程B |
---|---|
x = a | y = b |
b =1 | a = 2 |
正常的结果:x = 0; y = 0; 但是可能由于指令重排出现以下结果:
线程A | 线程B |
---|---|
b = 1 | a = 2 |
x = a | y = b |
指令重排导致的诡异结果: x = 2; y = 1;
非计算机专业
volatile 可以避免指令重排:
内存屏障。CPU指令。作用:
volatile 是可以保证可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
volatile 内存屏障在单例模式中使用的最多!
动动小脑袋瓜的单列总结
什么是单列 , 为什么用到单列模式?
单列模式就是一个类只有一个实列对象, 为什么用到单例模式 , 对性能进行的优化,比如连接数据库的类 本来就是一个常需要用的类,经常销毁和创建那么就非常消耗资源,这个类完全是可以重复使用的,那么就可以用单列模式,这样就可以不用消耗资源
如何实现单例模式
单例模式有两种写法 一种就是懒汉式 另一种就是饿汉式
懒汉式顾名思义 就是非常懒 他需要你要调用才去创建对象
饿汉式就是一上来就直接创建对象
饿汉式这样的不好 就是假如是一个比较消耗资源的对象 ,假如又没有用到这个对象 ,就比较浪费
更加合理的就是懒汉式 只有调用的时候才去创建 比较好
线程安全问题
懒汉式 线程不安全 饿汉式线程安全
饿汉式为什么是线程安全的呢 ? 懒汉式为什么又不是线程安全的呢?
饿汉式是线程安全的:由于一个类在整个生命周期只会被加载一次,那么这个单例只会创建一个实列,线程每次拿也只拿到这个唯一实列。
懒汉式不是线程安全的: 因为调用才创建对象 , 那么多个线程同时调用的话 ,就会造成实例化多次, 可以在方法上加入synchronize,就可以保证线程安全, 但是每次调用方法都排队争取锁影响性能,优化就可以用DCL双重检测模式,通过两个if 判断对象是否被创建来保证线程安全, 最好加一个volatile 防止指令重拍 , 正确执行路线应该是1 创建一个内存空间 2 实例化对象 3 把对象指到内从空间里, 不加volatile 的话 ,jvm 为了性能可能就是132 然后另外一个线程在实例化对象 这样的话就会多次创建对象,
反射的话 还是可以破坏单例模式
枚举类 就是典型的单列模式,他是不能被反射破坏的
//懒汉式
class LHan{
private LHan(){
System.out.println(Thread.currentThread().getName());
}
private volatile static LHan lhan = null;
//volatile 可见 不能保证线程安全 原子性 双层检测所模式 DLC
public static synchronized LHan getInstance(){
if (lhan==null){
synchronized (LHan.class){
if (lhan==null) {
lhan = new LHan();
}
}
}
return lhan;
}
}
//饿汉式
class hungry{
//一上来就要私有化构造
private hungry(){
}
private static final hungry instance = new hungry();
public static hungry getInstance(){
return instance;
}
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
使用CAS会造成ABA问题,一个线程a将数值改成了b,接着又改成了a,当另外一个线程用a对比的时候是正确的,认为是没有变化的,其实是已经变化过了,这种过程就叫ABA问题。
解决:加版本号 version 每次更改都+1
通过以上,大家都知道CAS虽然很高效解决了原子操作问题,但是CAS仍然存在问题