JUC
1.什么是JUC
-
java.util.concurrent并发
-
java.util.concurrent.atomic
-
java.util.concurrent.locks
java.util工具包,包,分类
业务:普通的线程代码 Thread
Runnable 没有返回值,效率相比于Callable相对较低
2.线程和进程
进程:一个程序,例:QQ.EXE,代表程序的集合
一个进程往往可以包含多个线程,至少包含一个!
java默认有几个线程?2个 main,GC
线程:系统资源调度和分配的最小单位
java开不了线程,他会调用本地的native方法(底层c++)开启,java不能直接操作硬件,java运行在jvm中
并发/并行
并发编程:并发,并行
并发(多个线程操作同一资源)
- cpu一核,模拟出来的多条线程,快速交替
并行(多个人一起行走)
- cpu多核,多个线程可以真正的同时执行;线程池
并发编程的本质:充分利用cpu的资源
线程有几个状态?
public enum State {
//线程新生
NEW,
//运行状态
RUNNABLE,
//阻塞状态
BLOCKED,
//等待,死死的等
WAITING,
//超时等待
TIMED_WAITING,
//中止
TERMINATED;
}
wait/sleep区别
1.来自不同的类
wait=>Object
sleep=>Thread
企业当中,休眠用:TimeUnit.单位.sleep(几个单位);
2.关于锁的释放
wait会释放锁,
sleep睡觉了,抱着锁一起睡,不释放
3.使用的范围是不同的
wait必须在同步代码块中
sleep可以在任何地方睡
4.是否需要捕获异常
wait 不需要
sleep 必须要捕获异常
3.Lock锁(重点)
传统 Synchronized
看juc卖票代码
lock
lock接口
l.lock()加锁
l.unlock()解锁
lock实现类
ReentrantLock:可重入锁
ReentrantLockReadWriteLock.ReadLock:读锁
ReentrantLockReadWriteLock.WriteLock:写锁
公平/非公平锁
FairSync:公平锁,非常公平,可以先来后到
NonFairSync:非公平锁,非常不公平,可以插队
Synchronized 和 Lock 区别
1.Synchronized 时内置的java关键字,Lock是一个java类
2.Synchronized 无法判断获取锁的状态,而Lock可以判断是否获得到了锁
3.Synchronized 会自动释放锁,Lock必须手动释放锁,!如果不释放,死锁
4.Synchronized 线程1(获得锁),线程2(等待,傻傻的等),Lock锁就不一定等待下去
5.Synchronized 可重入锁,不可中断的,非公平锁,lock 可重入的,可以判断锁,可公平可不公平
6.Synchronizde 适合锁少量的代码同步问题,Lock适合锁大量的同步代码!
锁是什么?如何判断锁的是谁?
4.生产者和消费者问题
面试常见问题:单例模式,排序算法,生产者和消费者,死锁问题
Synchronized版本
/**
* 线程之间的通信问题:生产者和消费者问题
* 线程交替执行A,B操作同于一个变量 num = 0
* A num++
* B num--
* A num++
* .
* .
* .
* 交替执行
*
* 口诀,判断等待通知
* */
public class Test {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
while (true){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
while(true){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Data{
private int number = 0;
public synchronized void increment() throws InterruptedException {
if (number != 0) {
//todo 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"执行后number = "+number);
this.notify();
//todo 通知其他线程执行完毕
}
public synchronized void decrement() throws InterruptedException {
if(number == 0){
//todo 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"执行后number = "+number);
this.notify();
//todo 通知其他线程执行完毕
}
}
问题,两个线程以上if要换成while,看文档,wait总应该出现在循环中
正确代码:
class Data{
private int number = 0;
public synchronized void increment() throws InterruptedException {
while (number == 1) {
//todo 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"执行后number = "+number);
this.notify();
//todo 通知其他线程执行完毕
}
public synchronized void decrement() throws InterruptedException {
while (number == 0){
//todo 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"执行后number = "+number);
this.notify();
//todo 通知其他线程执行完毕
}
}
JUC版生产者和消费者问题
代码:
class Data2 {
private int number = 0;
private Lock lock;
private Condition toAdd;
private Condition toLess;
public Data2(){
lock = new ReentrantLock();
toAdd = lock.newCondition();
toLess = lock.newCondition();
}
public void increment() throws InterruptedException {
lock.lock();
try{
while(number == 1){
toAdd.await();
}
number ++;
System.out.println(Thread.currentThread().getName() + "执行后number = " + number);
toLess.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try{
while(number == 0){
toLess.await();
}
number --;
System.out.println(Thread.currentThread().getName() + "执行后number = " + number);
toAdd.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
5.八锁现象
如何判断锁的是谁!永远知道什么锁,锁到底锁的谁
深刻理解锁是什么
1.synchronized锁的对象是方法的调用者
6.集合类不安全
List
public class ListTest {
public static void main(String[] args) {
//List<String> 线程安全集合.map.set.list = new ArrayList<>(); 线程不安全
// List<String> 线程安全集合.map.set.list = new Vector<>(); synchronized实现
// List<String> 线程安全集合.map.set.list = Collections.synchronizedList(new ArrayList<>());
List<String> list = new CopyOnWriteArrayList<>();
//copyOnWrite 写入时复制
//多个线程调用的时候,线程安全集合.map.set.list,读取时固定,写入覆盖
//在写入时避免覆盖,造成数据问题
//读写分离
for (int i = 0; i < 10; i++) {
new Thread(()-> {
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
new Thread(()-> {
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
new Thread(()-> {
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
set
public class SetTest {
public static void main(String[] args) {
// Set<String > 线程安全集合.map.set = new HashSet<>(); 线程不安全
// Set<String > 线程安全集合.map.set = Collections.synchronizedSet(new HashSet<>()); synchronized 实现
Set<String > set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 100; i++) {
new Thread(() ->{
set.add(UUID.randomUUID().toString());
System.out.println(set);
},String.valueOf(i)).start();
new Thread(() ->{
set.add(UUID.randomUUID().toString());
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
map
public class MapTest {
public static void main(String[] args) {
//map怎么用,默认等价于什么 工作中不用hashmap
//默认等价于 : HashMap<String,String> map = new HashMap<>(16,0.75f);
//Map<String,String> map = new HashMap<>(); 线程不安全
//Map<String,String> map = Collections.synchronizedMap(new HashMap<>()); synchronized实现
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
map.put(UUID.randomUUID().toString(),UUID.randomUUID().toString());
System.out.println(map);
},String.valueOf(i)).start();
new Thread(() -> {
map.put(UUID.randomUUID().toString(),UUID.randomUUID().toString());
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
7.Callable
1.可以有返回值
2.可以抛出异常
3.方法不同,run()/call()
使用方法:使用FutureTask中转,FutureTask可以接受call的返回值,用get方法取得返回值
get方法可能会超时
public class CallableTest {
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
new Thread(futureTask).start();
new Thread(futureTask).start(); //结果会被缓存,效率高,调用两次只会调用一次
try {
System.out.println(futureTask.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("Call");
return 123456;
}
}
8.常用的辅助类
8.1 CountDownLatch(减法计数器)
减法计数器,当count为0时才执行后续操作,如果执行次数没到就等待
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"out");
//数量减一
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待计数器归零
countDownLatch.await();
System.out.println("close");
}
}
原理:countdown()数量-1
await()//等待数量归零
每次有线程调用countdown数量-1,假设计数器变为0,countdownlatch.await会被唤醒,继续执行
8.2 CyclicBarrier(加法计数器)
加法计数器,当执行到指定次数时执行放入CyclicBarrier中的Runabble方法
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤成功");
});
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()-> {
System.out.println(finalI +"召唤");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
8.3 Semaphore(信号量)
限流,最多多少个线程,到达指定数量之后等待之前某个线程结束才能继续执行新的线程
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数量,停车位,限流作用
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
//获得
semaphore.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();
}
}
}
原理:acquire()获得,假设如果以及满了,等待,等待被释放为之
relrese() 释放,会将当前的信号量释放+1.然后唤醒等待的线程
作用:多个共享资源互斥使用
并发限流
9.读写锁
ReadWritelock
/**
* ReadWriteLock
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
* 读 - 读 : 可以共存
* 读 - 写 : 不能共存
* 写 - 写 : 不能共存
* **/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache cache = new MyCache();
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
cache.put(""+finalI,""+finalI);
},String.valueOf(i)).start();
}
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
cache.get(""+finalI);
},String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
//存 / 写
public void put(String key,Object value){
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"正在写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入OK");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.writeLock().unlock();
}
}
//取 / 读
public Object get(String key){
lock.readLock().lock();
Object o = null;
try{
System.out.println(Thread.currentThread().getName()+"正在读取"+key);
o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK"+key);
}catch (Exception e){
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
return o;
}
}
10.阻塞队列
10.1 ArrayBlockingQueue 阻塞队列
写入:如果队列满了,就必须阻塞等待
取:如果队列是空的,必须阻塞等待生产
什么时候我们使用阻塞队列:并发任务,线程池
使用(添加/删除):
四组API
1.抛出异常
2.不会抛出异常
3.阻塞等待
4.超时等待
方式 | 抛出异常 | 有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(obj,TIME,TIMEUTIL) |
移除 | remove() | poll() | take() | poll(TIME,TIMEUTIL) |
判断队列首 | element() | peek() |
public class Test {
public static void main(String[] args) {
try {
new Test().test4();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 抛出异常 会输出错误信息
public void test1(){
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//h会抛出异常
/*Exception in thread "main" java.lang.IllegalStateException: Queue full
at java.util.AbstractQueue.add(AbstractQueue.java:98)
at java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:312)
at 阻塞队列.Test.test1(Test.java:17)
at 阻塞队列.Test.main(Test.java:7)*/
}
//不抛出异常 会返回false
public void test2(){
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
//存
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.peek());
//取
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//false
}
/*
* 阻塞 - 等待
* */
public void test3() throws InterruptedException {
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3);
queue.put(1);
queue.put(1);
queue.put(1);
//一直阻塞
//queue.put(1);
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
queue.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//如果不加入就等待
System.out.println(queue.take());
}
/*
* 超时等待
* */
public void test4() throws InterruptedException {
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.offer(1,3,TimeUnit.SECONDS));
System.out.println(queue.offer(1,3,TimeUnit.SECONDS));
System.out.println(queue.offer(1,3,TimeUnit.SECONDS));
System.out.println(queue.offer(1,3,TimeUnit.SECONDS));
System.out.println(queue.poll(3,TimeUnit.SECONDS));
System.out.println(queue.poll(3,TimeUnit.SECONDS));
System.out.println(queue.poll(3,TimeUnit.SECONDS));
System.out.println(queue.poll(3,TimeUnit.SECONDS));
}
}
10.2 SynchronizedQueue 同步队列
没有容量,进去一个元素必须等待取出来之后才能再往里面放新的元素
public class Test {
public static void main(String[] args) {
BlockingQueue<Integer> queue =new SynchronousQueue<Integer>();
new Thread(()->{
try {
queue.put(1);
System.out.println("put1ok");
queue.put(2);
System.out.println("put2ok");
queue.put(3);
System.out.println("put3ok");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
11.线程池(重要)
池化技术
程序的运行,本质:占用系统资源!优化资源的使用! 演进出一种策略=》池化技术
例如:线程池 ,连接池,内存池,对象池//。。。创建销毁十分浪费资源
对开启和关闭比较消耗资源的东西,事先准备好资源,有人要用就拿,用完还回来
线程池的好处:
1.降低资源的消耗
2.提高相应速度
3.方便管理
线程可以复用,可以控制最大并发数,管理线程
三大方法
public class Test {
public static void main(String[] args) {
//ExecutorService executor = Executors.newSingleThreadExecutor(); //单个线程
//ExecutorService executor = Executors.newFixedThreadPool(2);//固定线程数的线程池
ExecutorService executor = Executors.newCachedThreadPool();//可伸缩的,遇强则强,遇弱则弱
try{
for (int i = 0; i < 10; i++) {
//使用线程池后,用线程池来创建线程
executor.execute(()->{
System.out.println(Thread.currentThread().getName() + "ok");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
executor.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!
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;
}
ThreadPoolExecutor中有七个参数
int corePoolSize : 核心线程池大小
int maximumPoolSize : 最大的线程池大小
long keepAliveTime : 线程存活时间(超时了没有人调用就会释放)
TimeUnit unit : 释放时间的单位
BlockingQueue<Runnable> workQueue : 阻塞队列
ThreadFactory threadFactory : 线程工厂(创建线程的,一般不用动)
RejectedExecutionHandler handler : 拒绝策略
四种拒绝策略
AbortPolicy 线程和队列都满了就不处理这个人的并抛出异常
CallerRunsPolicy 线程和队列都满了就让他哪来的去哪里(由调用的线程处理方法体中的任务)
DiscardPolicy 队列满了不会抛出异常,队列满了就会丢掉任务
DiscardOldestPolicy 也不会抛出异常,队列满了会尝试和最早的竞争,竞争成功就执行,失败就抛弃
最大线程该如何定义
1.CPU密集型 ,cpu线程数就定义为几,cpu效率最高
2.IO密集型 , 判断程序中十分耗io的线程, 设置为两倍于耗io线程的数量
程序 15个大型任务,io十分占用资源,那就最少30个线程处理
12.四大函数式接口(必须掌握)
泛型,枚举,反射
lambda表达式
链式编程
函数式接口 : 只有一个方法的接口
在java中出现极多
新版本框架底层大量应用
foreach(消费者类型的函数式接口)
Stream流式计算
四大函数式接口
Function 函数式接口
public class FunctionDemo {
public static void main(String[] args) {
//返回参数的接口
/*Function<String ,String> function = new Function<String, String>() {
@Override
public String apply(String s) {
return s;
}
};*/
/*Function<String,String> function = (s)->{
return s;
};*/
Function<String,String> function = s->{
return s;
};
System.out.println(function.apply("123"));
}
}
predicate 断定型接口
public class PredicateDemo {
public static void main(String[] args) {
//返回输入是否等于0的接口
/*Predicate<Integer> predicate =new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer == 0;
}
};*/
/*Predicate<Integer> predicate = (a)->{
return a==0;
};*/
Predicate<Integer> predicate = a->{
return a==0;
};
System.out.println(predicate.test(0));
System.out.println(predicate.test(1));
}
}
Consumer 消费型接口
public class ConsumerDemo {
public static void main(String[] args) {
//输出传入的参数的接口
// Consumer<Integer> consumer = new Consumer<Integer>() {
// @Override
// public void accept(Integer integer) {
// System.out.println(integer);
// }
// };
// Consumer<Integer> consumer = (a)->{
// System.out.println(a);
// };
// Consumer<Integer> consumer = a->{
// System.out.println(a);
// };
Consumer<Integer> consumer = System.out::println;
consumer.accept(123);
}
}
Supplier 生产型接口
public class SupplierDemo {
public static void main(String[] args) {
/*Supplier<Double> supplier = new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
};*/
/*Supplier<Double> supplier = ()->{
return Math.random();
};*/
Supplier<Double> supplier = Math::random;
System.out.println(supplier.get());
}
}
13.Stream流式计算
什么是流式计算
大数据:存储 + 计算
存储:集合,Mysql 本质就是存东西的
计算都应该交给流操作!
/**
* 题目要求 : 一行代码实现
* 现有五个用户!筛选:
* 1.id必须是偶数
* 2.年龄呢必须大于21岁
* 3.用户名转换为大写字母
* 4.用户名字母倒着排序
* 5.只输出一个用户
* **/
public class Test {
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(2,"b",22);
User u3 = new User(3,"c",23);
User u4 = new User(4,"d",24);
User u5 = new User(6,"e",25);
//集合只需要存储
List<User> list = Arrays.asList(u1,u2,u3,u4,u5);
//流负责操作
//lambda表达式,链式编程,函数式接口,Stream流式计算
list.stream().
filter(u->{return u.getId()%2 == 0;}).
filter(u->{return u.getAge() > 21;}).
peek((u)-> u.setName(u.getName().toUpperCase())).
sorted((a,b)->{return b.getName().charAt(0) - a.getName().charAt(0);}).
limit(1).
forEach(System.out::println);
}
}
14.ForkJoin
什么是ForkJoin
ForkJoin出现在jdk1.7,并行执行任务!提高效率,处理大数据量!
大数据:Map Reduce(把大任务分成小任务) – 分治的思想
ForkJoin特点:工作窃取
这个里面维护的都是双端队列,b执行完之后可以存a队列的另一端偷任务去执行
Test类
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.LongStream;
public class Test {
static long start = 0;
static long end = 0;
public static void main(String[] args) {
start = 1;
end = 10_0000_0000;
test1();
test2();
test3();
}
public static void test1(){
long start = System.currentTimeMillis();
long sum = 0;
for(long i = Test.start; i <= end ; i++){
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间 : " + (end - start));
}
public static void test2(){
long start = System.currentTimeMillis();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinDemo task = new ForkJoinDemo(Test.start,Test.end);
pool.execute(task);
long sum = 0;
try {
sum = task.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间 : " + (end - start));
}
public static void test3(){
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(Test.start,Test.end).parallel().reduce(0,Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+sum+" 时间 : " + (end - start));
}
}
forkJoin类
/**
*
* 求和计算任务
* 如何使用ForkJoin
* 1.ForkJoinPool通过它执行
* 2.ForkJoinTask
*
* **/
public class ForkJoinDemo extends RecursiveTask<Long> {
private long start;
private long end;
//临界值
private long temp = 10000L;
public ForkJoinDemo(Long start,Long end){
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if((end -start) > temp){
//todo 分支合并计算
long mid = (start + end) / 2 ;
ForkJoinDemo task1 = new ForkJoinDemo(start,mid);
task1.fork();//拆分任务,把线程加入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(mid + 1,end);
task2.fork();//拆分任务,把线程加入线程队列
return task1.join() + task2.join();
}else{
long sum = 0;
for (long i = start; i <=end ; i++) {
sum += i;
}
return sum;
}
}
}
15.异步回调
Future 设计的初衷:对某个事件的返回结果进行建模
//异步调用 : CompletableFuture
//异步执行
//成功回调
//失败回调
// runAsync 无返回值的异步任务
// supplyAsync 有返回值的异步任务
public class Demo01 {
public static void main(String[] args) {
// CompletableFuture<Void> future = CompletableFuture.runAsync(()-> {
// try {
// TimeUnit.SECONDS.sleep(3);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("async run") ;
// });
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("Main run");
// try {
// future.get();
// } catch (InterruptedException | ExecutionException e) {
// e.printStackTrace();
// }
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()-> {
//int a = 1/0;
return 1024;
});
try {
System.out.println(future.whenComplete((w, u) -> { }).exceptionally((e) -> {
return 233;
}).get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
16.JMM
请你谈谈对Volatile的理解
Volatile 是java虚拟机提供的轻量级的同步机制
1.保证可见性
2.不保证原子性
3.禁止指令重排
什么是JMM
JMM:java内存模型,不存在的东西,概念!约定!
关于JMM的一些同步的约定:
1.线程解锁前,必须把共享变量**立刻**刷会主存。
2.线程加锁前,必须读取主存中的最新值到工作内存中!
3.加锁和解锁是同一把锁。
线程 主存,工作内存
问题:线程b修改了值,但线程a不能及时可见
内存交互操作有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操作之前,必须把此变量同步回主内存
JMM对这八种操作规则和对volatile的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析。
17.Volatile
可见性
//可见性测试
public class VolatileTest {
//程序不知道内存中的值已经被修改过了
private static volatile 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);
}
}
2.不保证原子性
原子性:不可分割
线程a在执行的时候,不能被打扰
atomic原子类,这些类底层与操作系统挂钩,在内存中修改值!Unsafe类是一个很特殊的存在
//不保证原子性
public class VolatileTest1 {
private static AtomicInteger num = new AtomicInteger(0);
//1.synchronized能保证值为20000
//2.volatile不保证原子性
//3.使用原子类解决原子性问题
public static void add(){
//num++;//不是一个原子性操作
num.getAndIncrement();//1.方法:CAS
}
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(num);
}
}
18.单例模式
饿汉式 DCL懒汉式
饿汉式
public class Hungry {
private byte[] data1 = new byte[1048576];
private byte[] data2 = new byte[1048576];
private byte[] data3 = new byte[1048576];
private byte[] data4 = new byte[1048576];
private static final Hungry HUNGRY = new Hungry();
private Hungry() {
}
public static Hungry getInstance() {
return HUNGRY;
}
}
DCL懒汉式
public class Lazy {
private static boolean key = false;
private static volatile Lazy LAZY;
private Lazy() {
Class var1 = Lazy.class;
synchronized(Lazy.class) {
if (!key) {
key = true;
} else {
throw new RuntimeException("不要试图使用反射破环异常");
}
}
}
public static Lazy getInstance() {
if (LAZY == null) {
Class var0 = Lazy.class;
synchronized(Lazy.class) {
if (LAZY == null) {
LAZY = new Lazy();
}
}
}
return LAZY;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Constructor<Lazy> constructor = Lazy.class.getDeclaredConstructor();
constructor.setAccessible(true);
Lazy instance2 = (Lazy)constructor.newInstance();
Field key = Lazy.class.getDeclaredField("key");
key.set(instance2, false);
System.out.println(instance2);
System.out.println(getInstance());
}
}
静态内部类
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return Holder.InnerClass.HOLDER;
}
public static void main(String[] args) {
}
public static class InnerClass {
private static final Holder HOLDER = new Holder();
public InnerClass() {
}
}
}
单例不安全,因为有反射 所以要用枚举 :
枚举的最终反编译源码
// 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 53554F8B6A215F0F;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(53554F8B6A215F0F/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
枚举保证线程安全,不会被反射入侵
19.深入理解CAS
什么是CAS ?
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
//CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
// expect期望,update更新
// 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());
}
}
CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么执行操作,如果不是就一直循环!
缺点:
1.由于底层是自旋锁,循环会耗时
2.一次性只能保证一个共享变量的原子性
3.ABA问题
ABA问题:
A要把变量改为2,在A的cas操作中,B线程把A改为3又改回了1,A对此不知情
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
//CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
// 对于我平时写的sql : 乐观锁!
// expect期望,update更新
// 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, 6666));
System.out.println(atomicInteger.get());
}
}
20.原子引用
带版本号的原子操作!解决ABA问题,引入原子引用,对应的思想:乐观锁
注意
Integer 使用了对象缓存机制,默认范围是-128-127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valuerOf使用缓存,而new一定会为创建新的对分配新的内存空间;
public class CASDemo {
//CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
//AtomicInteger atomicInteger = new AtomicInteger(2020);
//Integer 在-128到127之间是同一个对象,超过这个范围就会是新的对象
//正常的业务操作中,比较的都是一个个对象
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1,1);
new Thread(()->{
//获得版本号
System.out.println("a1->"+ atomicInteger.getStamp());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicInteger.compareAndSet(1,3,atomicInteger.getStamp(),atomicInteger.getStamp() + 1);
System.out.println("a2->"+atomicInteger.getStamp());
atomicInteger.compareAndSet(3,1,atomicInteger.getStamp(),atomicInteger.getStamp() + 1);
System.out.println("a3->"+atomicInteger.getStamp());
},"a").start();
//和乐观锁的原理相同
new Thread(()->{
//获得版本号
int stamp = atomicInteger.getStamp();
System.out.println("b1->"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(1,2,stamp,stamp+1));
},"b").start();
}
}
21.各种锁的理解
1.公平锁,非公平锁
公平锁:非常公平,不能插队
非公平锁,可以插队,默认都是非公平锁
2.可重入锁
可重入锁(递归锁)
3.自旋锁
spinLock
//自旋锁
class MyLock{
//加锁,解锁
AtomicReference<Thread> atomicReference = new AtomicReference<>();
private int count = 0;
public void lock(){
Thread thread = Thread.currentThread();
if(atomicReference.get() == thread){
count ++;
return;
}
while(!atomicReference.compareAndSet(null,thread)){
}
System.out.println(thread.getName() + "加锁成功");
}
public void unlock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "解锁了");
if(atomicReference.get() == thread){
if(count > 0){
count --;
}else{
atomicReference.compareAndSet(thread,null);
}
}
}
}
class Test{
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
}).start();
new Thread(()->{
phone.sms();
}).start();
}
}
class Phone{
MyLock lock = new MyLock();
public void sms(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "sms");
phone();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void phone(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + "phone");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
4.死锁
死锁是什么
a有a的锁,b有b的锁
他们都想抢占对方的锁,就会死锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class Demo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sLock1();
}).start();
new Thread(() -> {
phone.sLock2();
}).start();
}
}
class Phone{
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
public void sLock1(){
lock1.lock();
try{
System.out.println(Thread.currentThread().getName() + "sLock1");
TimeUnit.SECONDS.sleep(1);
sLock2();
}catch (Exception e){
e.printStackTrace();
}finally {
lock1.unlock();
}
}
public void sLock2(){
lock2.lock();
try{
System.out.println(Thread.currentThread().getName() + "sLock2");
TimeUnit.SECONDS.sleep(1);
sLock1();
}catch (Exception e){
e.printStackTrace();
}finally {
lock2.unlock();
}
}
}
死锁测试,怎么排除死锁:
1.使用jps定位进程号
2.使用jstack 进程号 查看信息
排查问题: 1.日志 2.堆栈信息
完结撒花