JUC详解--【Foam番茄】
作者:互联网
1.什么是JUC
java.util 工具包
业务:普通的线程代码 Thread
Runnable 没有返回值,效率相比于 Callable 相对较低!
2.线程和进程
进程:一个程序,QQ.exe Music.exe 程序的集合
进程:一个进程往往可以包含多个线程,至少包含一个
java默认有几个线程?2个 一个main 一个GC
线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。
与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
进程和线程的根本区别是进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。 另外区别还有资源开销、包含关系、内存分配、影响关系、执行过程等。
java真的可以开启线程吗?不可以
本地方法,调用底层的c++,java无法直接操作硬件
并发,并行
并发编程:并发,并行
并发(多线程操作同一个资源)
- CPU一核,模拟出来多条线程,天下武功,唯快不破,快速交替
并行(多个人一起行走)
- CPU多核,多个线程可以同时执行
- 并发编程的本质:充分利用CPU的资源
线程有几个状态
public enum State {
NEW,//新生
RUNNABLE,//运行
BLOCKED,//阻塞
WAITING,//等待
TIMED_WAITING,//超时等待
TERMINATED;//终止
}
wait/sleep区别
1.来自不同的类
wait=>Object
sleep=>Thread
企业当中,休眠
TimeUnit操作
2.关于锁的释放
wait 会释放锁
sleep 睡觉了,抱着锁睡觉,不会释放锁
3.使用的范围是不同的
wait必须在同步代码块中
sleep可以在任何地方睡
4.是否需要捕获异常
wait不需要捕获异常
sleep需要捕获异常
Lock锁(重点)
线程就是一个单独的资源类,没有任何附属的操作
1.属性,方法
传统synchronized
Lock接口
公平锁:十分公平:可以先来后到
非公平锁:十分不公平:可以插队(默认)
Synchronized 和 Lock 区别
1.Synchronized 内置的java关键字,Lock是一个java类
2.Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.synchronized 会自动释放锁,Lock 必须要手动释放锁,如果不释放锁就会造成死锁
4.Synchronized 线程1(获得锁,阻塞),线程2(等待,一直等),Lock锁就不一定会等待下去,lock.tryLock
5.Synchronized 可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以设置)
6.synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码
锁是什么,如何判断锁的区别
4.生产者和消费者问题
生产者和消费者问题Synchronized版
面试的:单例模式,排序算法,生产者和消费者,死锁
if会造成虚假唤醒,用while可以避免
任何新技术,绝不可能只是覆盖了原来的技术,有自己的优势和补充
Condition 精准通知和唤醒线程
5.八锁现象
如何判断锁的是谁,永远的知道什么是锁
synchronized 锁的对象是方法的调用者
两个方法用的是同一个锁,谁先拿到谁执行
两个对象,两把锁,互相不干扰
new this 具体的一个对象
static Class 唯一的一个模板
6.集合类不安全
List不安全
并发下 ArrayList 不安全的
解决方案:
1.Vector解决 List
2.Collections解决 List
3.juc解决List
多个线程调用的时候,list,读取的时候,固定的,写入的时候可能存在覆盖操作
在写入的时候避免覆盖,造成数据问题
为什么CopyOnWriteArrayList比Vector效率高,因为Vector使用了Synchronized就会造成效率低,CopyOnWriteArrayList使用的Lock锁
set不安全
1.Set
2.Set
hashSet底层是什么?
hashset底层就是hashMap,set的本质就是map的key,key是无法重复的
map是怎样用的,加载因子,初始化容量
map不安全
工作中不用hashMap
默认等价于
Map<String,String>map=new HashMap<>(16,0.75);
1.Map<String,String> map= Collections.synchronizedMap(new HashMap<>());
2.Map<String,String> map=new ConcurrentHashMap<>();
7.Callable
1.可以有返回值
2.可以抛出异常
3.方法不同,run()/call()
package com.kuang.demo01.lock;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class callableTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// TODO Auto-generated method stub
new Thread().start();
MyThread thread=new MyThread();
FutureTask futureTask=new FutureTask(thread); // 适配类
new Thread(futureTask,"a").start();
Integer o=(Integer) futureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
// TODO Auto-generated method stub
return 1024;
}
}
1.有缓存
2.结果可能需要等待,会阻塞
8.常用的辅助类
CountDownLatch
package com.kuang.demo01.lock;
import java.util.concurrent.CountDownLatch;
// 计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 总数是6
CountDownLatch count=new CountDownLatch(6);
for(int i=1;i<=6;i++) {
new Thread(()->{
count.countDown(); // 数量-1
},String.valueOf(i)).start();
}
count.await(); // 等待计数器归零,然后再向下执行
}
}
原理:
count.countDown(); // 数量-1
count.await(); // 等待计数器归零,然后再向下执行
每次有线程调用countDown数量-1,假设计数器变为0,countDownLatch.await就会被唤醒,继续执行
CyclicBarrier
package com.kuang.demo01.lock;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 召唤龙珠的线程
CyclicBarrier cyclic=new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功");
});
for (int i = 0; i < 7; i++) {
final int temp=i;
new Thread(()-> {
System.out.println(temp);
try {
cyclic.await();
} catch (InterruptedException | BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}).start();
}
}
}
Semaphore 信号量
package com.kuang.demo01.lock;
import java.util.concurrent.Semaphore;
public class SemphoreDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 总共三个位置 限流
Semaphore sem=new Semaphore(3);
// 6个线程去抢占
for (int i = 0; i < 6; i++) {
new Thread(()-> {
// acquire 得到
try {
sem.acquire();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
sem.release(); // 释放
}
}).start();
}
}
}
sem.acquire(); 获得,假设如果已经满了,等待,直到被释放为止
sem.release(); 释放,会将当前的信号量释放+1,然后唤醒等待的线程
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数
9.读写锁
package com.kuang.demo01.lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
// 独占锁/共享锁 排他锁/共享锁
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache= new MyCache();
// 写入
for (int i = 0; i < 5; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp);
}).start();
}
// 读取
for (int i = 0; i < 5; i++) {
final int temp=i;
new Thread(()->{
myCache.get(temp+"");
}).start();
}
}
}
class MyCache{
private volatile Map<String,Object>map=new HashMap<String,Object>();
// 读写锁:更加细粒度的锁
private ReadWriteLock lock=new ReentrantReadWriteLock();
// 存,写
public void put(String key,Object value) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"ok");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
lock.writeLock().unlock();
}
}
// 取,读
public void get(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取");
Object o=map.get(key);
System.out.println(Thread.currentThread().getName()+"ok");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}
}
10.阻塞队列
方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(arg1,arg2,arg3) |
移除 | remove | poll | take | poll(arg1,arg2) |
判断队列首尾 | element | peek | - | - |
package com.kuang.demo01.bq;
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
test4();
}
// 抛出异常
public static void test1(){
// 队列的大小
ArrayBlockingQueue blockingQueue=new ArrayBlockingQueue<>(3);
blockingQueue.add("a");
blockingQueue.add("b");
blockingQueue.add("c");
// java.lang.IllegalStateException: Queue full 队列已满
// blockingQueue.add("d");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// java.util.NoSuchElementException 没有元素
// System.out.println(blockingQueue.remove());
// 检查队首 java.util.NoSuchElementException
System.out.println(blockingQueue.element());
}
// 有返回值,没有异常
public static void test2() {
// 队列的大小
ArrayBlockingQueue blockingQueue=new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
// false 不抛出异常
// blockingQueue.offer("d");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// null 取不到值
// System.out.println(blockingQueue.poll());
// 检查队首 null
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.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
// 没有这个元素,一直阻塞
// System.out.println(blockingQueue.take());
}
// 等待,阻塞(等待超时)
public static void test4() throws InterruptedException {
// 队列的大小
ArrayBlockingQueue blockingQueue=new ArrayBlockingQueue<>(3);
// 一直阻塞
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
// 队列没有位置了,等待超过两秒退出
// blockingQueue.offer("d",2,TimeUnit.SECONDS);
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// 等待超过两秒退出
// System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}
}
SynchronousQueue 同步队列
没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素
package com.kuang.demo01.bq;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 同步队列
* 和其他的BlockingQueue 不一样,SynchronousQueue 不存储元素
* put 了一个元素,必须从里面先take取出来,否则不能再put进去值
* 项目名称:doncic
* 类名称:SyschronousQueueDemo
* 类描述:
* 创建人:PC1
* 创建时间:2020年11月26日 上午10:31:01
* @version
*/
public class SyschronousQueueDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 同步队列
BlockingQueue<String> blockingQueue=new SynchronousQueue<String>();
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) {
// TODO Auto-generated catch block
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"t2").start();
}
}
11.线程池(重点)
线程池:3大方法,7大参数,4种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!优化资源的使用!
线程池,连接池,内存池,对象池,常量池……(无需频繁创建和销毁,十分浪费资源)
池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我
线程池的好处:
1.降低资源的消耗
2.提高响应的速度
3.方便管理
线程复用,可以控制最大并发数,管理线程
线程
三大方法
package com.kuang.demo01.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// Executors 工具类,3大方法
// 使用了线程池之后,要使用线程池来创建线程
public class Demo01 {
public static void main(String[] args) {
// 单个线程
// ExecutorService threadPool=Executors.newSingleThreadExecutor();
// 固定线程池的大小
// ExecutorService threadPool= Executors.newFixedThreadPool(3);
// 可伸缩的
ExecutorService threadPool= Executors.newCachedThreadPool();
try {
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
// 线程池用完,程序结束,关闭线程池
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,
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;
}
oom
内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
手动创建线程池
package com.kuang.demo01.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
// 四种拒绝策略
// new ThreadPoolExecutor.AbortPolicy() 不处理,直接抛出异常
// new ThreadPoolExecutor.CallerRunsPolicy() 哪来的去哪里
// new ThreadPoolExecutor.DiscardPolicy() 队列满了不会抛出异常,会丢掉任务
// new ThreadPoolExecutor.DiscardOldestPolicy() 队列满了,尝试去和最早的竞争,也不会抛出异常
public class Demo01 {
public static void main(String[] args) {
ExecutorService threadPool= new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy());
try {
// 最大承载:Deque + max
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
最大线程到底该如何定义
-
CPU 密集型 ,几核CPU就定义为几,可以保持CPU效率最高
Runtime.getRuntime().availableProcessors();
获取cpu核心数
-
IO 密集型,判断程序种十分耗IO的线程,一般设置该IO的两倍
12.四大函数式接口(必需掌握)
新时代程序员必须掌握:lambda表达式,链式编程,函数式接口,Stream流式计算
函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// 简化编程模型,在新版本的框架底层大量应用@FunctionalInterface
// foreach(消费者类的函数式接口)
四大原生函数式接口
-
Consumer
-
Function
-
Predicate
-
Supplier
function
package com.kuang.demo01.function;
import java.util.function.Function;
// Function 函数型接口,有一个输入参数,有一个输出
// 只要是函数型接口,可以用lamdba表达式简化
public class Demo01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 工具类:输出输入的值
/*Function<String,String> function=new Function<String,String>() {
@Override
public String apply(String str) {
// TODO Auto-generated method stub
return str;
}
};*/
// lambda 简化
Function function=(Str)->{return Str;};
System.out.println(function.apply("asdf"));
}
}
predicate
package com.kuang.demo01.function;
import java.util.function.Predicate;
// 断定型接口:有一个输入参数,返回值只能是布尔值
public class Demo02 {
public static void main(String[] args) {
// 判断字符串是否为空
/*Predicate<String> pre=new Predicate<String>() {
@Override
public boolean test(String t) {
// TODO Auto-generated method stub
return t.isEmpty();
}
};*/
Predicate<String> pre=t->t.isEmpty();
System.out.println(pre.test("123"));
}
}
consumer
package com.kuang.demo01.function;
import java.util.function.Consumer;
// consumer 消费型接口:只有输入,没有返回值
public class Demo03 {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*Consumer consumer=new Consumer<String>() {
@Override
public void accept(String t) {
// TODO Auto-generated method stub
System.out.println(t);
}
};*/
Consumer consumer=System.out::println;
consumer.accept("asdf");
}
}
Supplier
package com.kuang.demo01.function;
import java.util.function.Supplier;
// Supplier 供给型接口:没有参数只有返回值
public class Demo04 {
public static void main(String[] args) {
// TODO Auto-generated method stub
/*Supplier supplier=new Supplier<Integer>() {
@Override
public Integer get() {
// TODO Auto-generated method stub
return 1024;
}
};*/
Supplier supplier=()->1024;
System.out.println(supplier.get());
}
}
13.Stream流式计算
什么是Stream流式计算
大数据基本就是存储
+计算
集合,Mysql 本质就是存储东西的
真正的计算都应该交给流来操作
package com.kuang.demo01.stream;
public class User {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public User(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public User() {
super();
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
package com.kuang.demo01.stream;
import java.util.Arrays;
import java.util.List;
/**
* 筛选
* 1.id必须是偶数
* 2.年龄必须大于23岁
* 3.用户名转为大写字母
* 4.用户名字字母倒着排序
* 5.只输出一个用户
*
* 项目名称:doncic
* 类名称:Test
* 类描述:
* 创建人:PC1
* 创建时间:2020年11月26日 下午3:30:43
* @version
*/
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
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(5,"e",25);
User u6=new User(6,"f",26);
// 集合就是存储
List<User> list=Arrays.asList(u1,u2,u3,u4,u5,u6);
// 计算交给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);
}
}
14.ForkJoin 分支合并
什么是ForkJoin
ForkJoin在jdk1.7,并行执行任务!提高效率,大数据量
大数据:Map Reduce (把大任务拆分小任务)
ForkJoin 特点:工作窃取
这个里面维护的都是双端队列
ForkJoin操作
package com.kuang.demo01.forkjoin;
import java.util.concurrent.RecursiveTask;
// 求和计算的任务
/*
* 如何使用forkjoin
* 1.forkjoinpool 通过它来执行
* 2.计算任务 forkjoinPool.execute(ForkJoinTask task)
* 3.计算类要继承ForkJoinTask
*/
public class ForkJoinDemo extends RecursiveTask<Long>{
private Long start;
private Long end;
// 临界值
private Long temp=1000L;
public ForkJoinDemo(Long start, Long end) {
super();
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
// TODO Auto-generated method stub
if((end-start)<temp) {
Long sum=0L;
for (Long i = start; i <= end; i++) {
sum+=i;
}
return sum;
}else {
// 分支合并计算 递归
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.kuang.demo01.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 InterruptedException, ExecutionException {
test3();
}
//普通写法
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="+sum+"时间:"+(end-start));
}
// ForkJoin
public static void test2() throws InterruptedException, ExecutionException {
long start = System.currentTimeMillis();
ForkJoinPool fork=new ForkJoinPool();
ForkJoinTask<Long> demo=new ForkJoinDemo(0L, 10_0000_0000L);
// 提交任务
ForkJoinTask<Long> submit=fork.submit(demo);
//fork.execute(demo);
Long sum=submit.get();
long end = System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
// stream
public static void test3() {
long start = System.currentTimeMillis();
Long sum=LongStream.rangeClosed(0, 10_0000_0000).parallel().reduce(0,Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
}
15.异步回调
Future 设计的初衷:对将来的某个事件的时间进行建模
package com.kuang.demo01.future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
// 异步调用Ajax
/*
* 异步执行
* 成功回调
* 失败回调
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// TODO Auto-generated method stub
// 没有返回值的runAsync 异步回调
// 发起一个请求
/*CompletableFuture<Void> completeableFuture=CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
System.out.println(11);
// 获取阻塞执行结果
completeableFuture.get();*/
// 有返回值的supplyAsync异步回调
// ajax,成功和失败的回调
CompletableFuture<Integer> completeableFuture=CompletableFuture.supplyAsync(()->{
int i=1/0;
return 1024;
});
System.out.println(completeableFuture.whenComplete((t,u)->{
System.out.println(t+" "+u);
}).exceptionally((t)->{
t.getMessage();
return 233;
}).get());
}
}
16.JMM
谈谈对Volatile的理解
Volatile 是java虚拟机提供的轻量级的同步机制
1.保证可见性
2.不保证原子性
3.禁止指令重排
JMM java memory model
JMM: Java内存模型,不存在的概念
关于JMM的一些同步的约定:
1.线程解锁前,必须把共享变量立刻
刷回主存
2.线程加锁前,必须读取主存中的最新值到工作内存中
3.加锁和解锁是同一把锁
线程:工作内存,主内存
8种操作
内存交互操作有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操作之前,必须把此变量同步回主内存
17.Volatile
保证可见性
package com.kuang.demo01.forkjoin.volatiles;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
// 不加volatile 程序死循环
private volatile static int num=0;
public static void main(String[] args) {
new Thread(()-> { // 线程1 对主内存的变化不知道的
while(num==0) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
num =1;
System.out.print(num);
}
}
不保证原子性
原子性:ACID原则,不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割.要么同时成功,要么同时失败
package com.kuang.demo01.forkjoin.volatiles;
public class VDemo02 {
// 不保证原子性
private volatile static int num=0;
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()-> {
for(int j=0;j<1000;j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
public static void add() {
num++;
}
}
如果不加lock和synchronized,怎么样保证原子性
package com.kuang.demo01.forkjoin.volatiles;
import java.util.concurrent.atomic.AtomicInteger;
public class VDemo02 {
// 不保证原子性
private volatile static AtomicInteger num=new AtomicInteger();
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()-> {
for(int j=0;j<1000;j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
public static void add() {
// 不是一个原子性操作
// num++;
num.getAndIncrement();
}
}
这些类的底层都直接和操作系统挂钩,在内存中修改值,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 是可以保证可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生
18.彻底玩转单例模式
单例模式,顾名思义就是一个类只有一个实例,并且类负责创建自己的对象,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
为什么使用单例模式而不使用静态方法?
从面向对象的角度讲:
虽然都能实现目的,但是他们一个是基于对象,一个是面向对象的,就像我们不面相对象也能解决问题一样,面相对象的代码提供一个更好的编程思想。
如果一个方法和他所在类的实例对象无关,那么它就应该是静态的,反之他就应该是非静态的。如果我们确实应该使用非静态的方法,但是在创建类时又确实只需要维护一份实例时,就需要用单例模式了。
比如说我们在系统运行时候,就需要加载一些配置和属性,这些配置和属性是一定存在了,又是公共的,同时需要在整个生命周期中都存在,所以只需要一份就行,这个时候如果需要我再需要的时候new一个,再给他分配值,显然是浪费内存并且再赋值没什么意义,所以这个时候我们就需要单例模式或静态方法去维持一份且仅这一份拷贝,但此时这些配置和属性又是通过面向对象的编码方式得到的,我们就应该使用单例模式,或者不是面向对象的,但他本身的属性应该是面对对象的,我们使用静态方法虽然能同样解决问题,但是最好的解决方案也应该是使用单例模式。
饿汉式
package com.kuang.demo01.single;
// 饿汉式单例
public class Hungry {
// 可能会浪费空间
private byte[] data1=new byte[1024*1024];
private byte[] data2=new byte[1024*1024];
private byte[] data3=new byte[1024*1024];
private byte[] data4=new byte[1024*1024];
private Hungry() {
}
private final static Hungry HUNGRY=new Hungry();
public static Hungry getInstance() {
return HUNGRY;
}
}
DCL懒汉式
package com.kuang.demo01.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
// 懒汉式单例
public class LazyMan {
private static boolean foam=false;
private LazyMan() {
synchronized (LazyMan.class) {
if(foam==false) {
foam=true;
}else{
throw new RuntimeException("不要试图用反射破坏异常");
}
}
System.out.println(Thread.currentThread().getName()+" ok");
}
private volatile static LazyMan LAZYMAN;
// 双重检测锁模式的,DCL懒汉式
public static LazyMan getInstance() {
if(LAZYMAN==null) {
synchronized(LazyMan.class) {
if(LAZYMAN==null) {
// 不是一个原子性操作
LAZYMAN=new LazyMan();
/*
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
*
*/
}
}
}
return LAZYMAN;
}
// 反射
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
//LazyMan instance=LazyMan.getInstance();
Field foam=LazyMan.class.getDeclaredField("foam");
foam.setAccessible(true);
Constructor<LazyMan> declaredConstructor=LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1=declaredConstructor.newInstance();
LazyMan instance2=declaredConstructor.newInstance();
foam.set(instance1, false);
System.out.println(instance1);
System.out.println(instance2);
}
}
静态内部类
package com.kuang.demo01.single;
// 静态内部类
public class Holder {
private Holder() {
}
public static Holder getInstace() {
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER=new Holder();
}
}
单例不安全,因为有反射
package com.kuang.demo01.single;
// enum 是一个什么?本身也是一个Class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
枚举有两个参数,string和int
枚举类型的最终反编译源码是有参构造
19.深入理解CAS
什么是CAS
package com.kuang.demo01.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
// CAS compareAndSet:比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2020);
// 期望,更新
// public final boolean compareAndSet(int expect, int update)
// 如果我期望的值达到了,就进行更新,否则就不进行更新,CAS 是CPU的并发原语
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
unsafe
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
缺点:
1.循环会耗时
2.一次只能保证一个共享变量的原子性
3.ABA问题
CAS:ABA问题(狸猫换太子)
package com.kuang.demo01.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
// CAS compareAndSet:比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2020);
// 期望,更新
// public final boolean compareAndSet(int expect, int update)
// 如果我期望的值达到了,就进行更新,否则就不进行更新,CAS 是CPU的并发原语
// ==========================捣乱的线程=================================
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
// ==========================期望的线程=================================
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
20.原子引用
解决aba问题,引入原子引用!对应的思想:乐观锁
带版本号的原子操作
Integer 使用了对象缓存机制,默认范围是-128-127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间
package com.kuang.demo01.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo {
// CAS compareAndSet:比较并交换
public static void main(String[] args) {
// Integer
// 正常业务操作,里面比较的是一个对象
AtomicStampedReference<Integer> atomicInteger=new AtomicStampedReference<Integer>(1,1);
new Thread(()-> {
int stamp=atomicInteger.getStamp();// 获得版本号
System.out.println("a1=>"+stamp);
System.out.println(atomicInteger.compareAndSet(1, 2, atomicInteger.getStamp(), atomicInteger.getStamp()+1));
System.out.println("a2=>"+atomicInteger.getStamp());
System.out.println(atomicInteger.compareAndSet(2, 1, atomicInteger.getStamp(), atomicInteger.getStamp()+1));
System.out.println("a3=>"+atomicInteger.getStamp());
},"a").start();
new Thread(()-> {
int stamp=atomicInteger.getStamp();// 获得版本号
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(1, 6, stamp, stamp+1));
System.out.println("b2=>"+atomicInteger.getStamp());
},"b").start();
}
}
21.各种锁的理解
1.公平锁,非公平锁
公平锁:非常公平,不能插队,必须先来后到
非公平锁:非常不公平,可以插队(默认)
// 默认
public ReentrantLock() {
sync = new NonfairSync();
}
// 可选
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.可重入锁
可重入锁(递归锁)
拿到了外面的锁之后,就可以拿到里面的锁,自动获得
package com.kuang.demo01.lock;
public class Demo01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Phone phone=new Phone();
new Thread(()-> {
phone.sms();
}).start();
new Thread(()-> {
phone.call();
}).start();
}
}
class Phone{
public synchronized void sms() {
System.out.println("sms");
call();// 这里也有一把锁
}
public synchronized void call() {
System.out.println("call");
}
}
3.自旋锁
spinlock
package com.kuang.demo01.lock;
import java.util.concurrent.atomic.AtomicReference;
// 自旋锁
public class spinLockDemo {
AtomicReference<Thread> atomic=new AtomicReference<>();
// 加锁
public void myLock()
{
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"===> mylock");
// 自旋锁
while(!atomic.compareAndSet(null, thread)) {
}
}
// 解锁
public void myUnLock()
{
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"===> myUnLock");
// 自旋锁
atomic.compareAndSet(thread, null);
}
}
package com.kuang.demo01.lock;
import java.util.concurrent.TimeUnit;
public class TestSpinLock {
public static void main(String[] args) {
// 底层使用的自旋锁
spinLockDemo lock=new spinLockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.myUnLock();
}
},"t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
lock.myUnLock();
}
},"t2").start();
}
}
4.死锁
死锁是什么
死锁测试,怎么排除死锁:
1.互斥
2.占有等待
3.循环等待
4.不可抢占
解决问题
1.使用jps -l定位进程号
2.使用jstack 进程号查看进行信息
查看
1.日志
2.堆栈信息
标签:JUC,--,Foam,System,线程,println,new,public,out 来源: https://www.cnblogs.com/foamTomato/p/14053299.html