JUC并发编程
1.进程和线程
进程:是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。
线程:是进程的一个实体,是CPU调度和分配的基本单位。
1.1 线程
创建线程的几种方式
- 继承Thread类创建线程
- 实现Runnable接口创建线程
- 通过Callable和Future适配器创建线程
- 通过线程池创键线程
//实现Runnable接口创建线程 lambda表达式 重写run()方法
new Thread(() -> {
System.out.println("successful");
},"线程A").start();
//通过Callable和Future适配器创建线程 重写call()方法
FutureTask<String> task = new FutureTask<>(() -> {
System.out.println("callable");
return "hh";
});
new Thread(task).start();
System.out.println(task.get());
}
//Runable 与 Callable的区别
/*1.Callable 有返回值
2.可以抛出异常
*/
1.2 Thread类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2BQgJX6p-1627480327886)(C:\Users\W\AppData\Roaming\Typora\typora-user-images\1627450198762.png)]
Runnable 接口是唯一实现Thread方法的接口。
线程的状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hp92oRj6-1627480327888)(C:\Users\W\AppData\Roaming\Typora\typora-user-images\1627450533372.png)]
默认线程优先级NORM_PRIORITY = 5;
具有较高优先级的线程优先执行。
1.3 线程组
线程组管理线程,线程不能独立于线程组存在
Thread thread = new Thread(new ThreadGroup("hello"), "A");
ThreadGroup tg = thread.getThreadGroup();
while (tg.getParent() != null) {
tg = tg.getParent();
}
tg.list();
//结果
ThreadGroup采用树的结构管理着所有线程和线程组,顶层的是system,旗下有main组:
java.lang.ThreadGroup[name=system,maxpri=10](maxpri优先级)
Thread[Reference Handler,10,system] 引用处理
Thread[Finalizer,8,system] 终结器
Thread[Signal Dispatcher,9,system] 信号调度
Thread[Attach Listener,5,system] 附加监听器
java.lang.ThreadGroup[name=main,maxpri=10] 主线程组
Thread[main,5,main] 主线程
java.lang.ThreadGroup[name=hello,maxpri=10] 自定义hello线程组
Thread[t8,5,hello] hello组下的线程
1.4 重要方法
ClassLoader getContextClassLoader() 返回线程上下文的类加载器
getState() ;
getPriority() ;
isDaemon() ;
isAlive() ;
join() ;//插队 等到该线程结束
sleep(long millis) ;//线程睡眠 不释放锁
yield() ;//线程礼让,重新竞争,礼让不一定成功
1.5图解线程状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EyETRomk-1627480327889)(C:\Users\W\AppData\Roaming\Typora\typora-user-images\1627453598781.png)]
1.5 sleep()方法和wait()方法的区别
- sleep()方法是主动让出CPU,不会释放锁,在sleep指定时间后继续执行,wait方法则是主动释放锁,只有在调用lnotify方法才会重新唤醒,或者在wait时间结束。
- sleep() 方法可以在任何地方使用,而 wait() 方法则只能在同步方法或同步块中使用;
- sleep() 是线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait() 是 Object 的方法,调用会放弃对象锁,进入等待队列,待调用 notify()/notifyAll() 唤醒指定的线程或者所有线程,才会进入锁池,再次获得对象锁才会进入运行状态 。
1.6 在 Java 程序中怎么保证多线程的运行安全
线程安全在三个方面体现:
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,(atomic,synchronized);
可见性:一个线程对主内存的修改可以及时地被其他线程看到,(synchronized、volatile);
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before 原则,volatile禁止指令重排)。
2.Lock
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lmYRMJpu-1627480327893)(C:\Users\W\AppData\Roaming\Typora\typora-user-images\1627454577147.png)]
Lock相对于synchronized更加灵活,可以以任意顺序释放释放锁,从而使线程能够按照一定顺序执行。
2.1 公平锁与非公平锁
公平锁: 十分公平,必须先来后到~;
非公平锁: 十分不公平,可以插队;(默认为非公平锁)
2.2 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适合锁大量的同步代码;
2.3 虚假唤醒
如果使用if来判断可能会发生虚假唤醒,因此使用while
2.4 lock可以关联多个condition
精准的通知和唤醒线程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionTest {
public static void main(String[] args) {
Data data = new Data();
new Thread(data::PrintA, "A").start();
new Thread(data::PrintB, "B").start();
new Thread(data::PrintC, "C").start();
}
}
class Data {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int num = 1; //设置标志位
public void PrintA() {
lock.lock();
try {
while (num != 1) condition1.await();
System.out.println(Thread.currentThread().getName() + "===>A" );
num = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void PrintB() {
lock.lock();
try {
while (num != 2) condition2.await();
System.out.println(Thread.currentThread().getName() + "===>B" );
num = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void PrintC() {
lock.lock();
try {
while (num != 3) condition3.await();
System.out.println(Thread.currentThread().getName() + "===>C" );
num = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
2.5 锁的对象
明确锁的是什么
普通同步方法锁的是对象(最终锁的是方法的调用者,即对象实列);
静态同步方法锁的是class类,锁的是唯一对象模板。
3.集合不安全
3.1 List不安全
ArrayList在并发条件下是不安全的
-
可以使用Collections工具类下synchronized包装的list类
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
-
Vector是线程安全的,但是底层使用Synchronized关键字来实现,效率低下,不推荐使用。
- CopyOnWriteArrayList 写入时复制
CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!
3.2 set不安全
- 使用Collections工具类的synchronized包装的Set类
- 使用CopyOnWriteArraySet 写入复制的JUC解决方案
3.3 map不安全
-
ConcurrentHashMap 线程安全
-
HashTable线程安全
3.4 Java集合的快速失败机制
Java集合中有一个modCount变量,通过检测modCount变量是否为ExceptModCount来判断是否快速失败,抛出异常java.util.ConcurrentModificationException
4.常用的辅助类
4.1 CountDownLatch (倒计时锁)
- A
CountDownLatch
是一种通用的同步工具,可用于多种用途。一个CountDownLatch
为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用await
在门口等待,直到被调用countDown()
的线程打开。一个CountDownLatch
初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "==> Go Out");
countDownLatch.countDown(); // 每个线程都数量 -1
},String.valueOf(i)).start();
}
countDownLatch.await(); // 等待计数器归零 然后向下执行
System.out.println("close door");
}
}
//主要方法
/*countDown() 减少锁存器的计数,如果计数达到零,释放所有等待的线程。
*await() 导致当前线程等到锁存器计数到零,除非线程是 interrupted 。
*/
4.2 CyclickBarrier (周期性屏障)
利用CyclicBarrier类可以实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操作。下图演示了这一过程。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class CyclicBarrierTest {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(6, () -> {
System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
System.out.println("successful");
});
for (int i = 0; i < 18; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "get " + temp );
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
TimeUnit.SECONDS.sleep(1);
}
}
}
//运行结果
0get 0
1get 1
2get 2
3get 3
4get 4
5get 5
Thread.currentThread().getName() = 5
successful
6get 6
7get 7
8get 8
9get 9
10get 10
11get 11
Thread.currentThread().getName() = 11
successful
12get 12
13get 13
14get 14
15get 15
16get 16
17get 17
Thread.currentThread().getName() = 17
successful
//互相等待,到达预设的parties时,最后一个到达屏障的线程完成相应的动作
4.3 CountDownLatch 与 CyclickBarrier区别
- 在CyclicBarrier中线程调用await方法不仅会将自己阻塞还会将计数器减1,而在CountDownLatch中线程调用await方法只是将自己阻塞而不会减少计数器的值。
- CountDownLatch只能拦截一轮,而CyclicBarrier可以实现循环拦截。
4.4 Semaphore(信号量)
public class SemaphoreDemo {
public static void main(String[] args) {
// 线程数量,停车位,限流
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i <= 6; i++) {
new Thread(() -> {
// acquire() 得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位");
}catch (Exception e) {
e.printStackTrace();
}finally {
semaphore.release(); // release() 释放
}
}).start();
}
}
}
Thread-1抢到车位
Thread-0抢到车位
Thread-2抢到车位
Thread-0离开车位
Thread-2离开车位
Thread-1离开车位
Thread-5抢到车位
Thread-3抢到车位
Thread-4抢到车位
Thread-5离开车位
Thread-3离开车位
Thread-6抢到车位
Thread-4离开车位
Thread-6离开车位
Process finished with exit code 0
原理:
semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!
semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!
5.读写锁
使用ReadWriteLock 进行更细粒度的控制
ReadWriteLock用来维护一组相关联的lock
一次只允许一个线程修改,但是允许多个线程读取
package com.pt.lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static void main(String[] args) {
MyCash myCash = new MyCash();
for (int i = 1; i < 6; i++) {
final int temp = i;
new Thread(() -> {
myCash.write(temp, temp + 10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
for (int i = 1; i < 6; i++) {
final int temp = i;
new Thread(() -> {
myCash.read(temp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
class MyCash {
private final Map<Integer, Integer> map = new HashMap<>();
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock lock = new ReentrantLock();
public void write(int k, int v) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + k);
map.put(k, v);
System.out.println(Thread.currentThread().getName() + "写入成功!");
} finally {
readWriteLock.writeLock().unlock();
}
}
public void read(int k) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + k);
map.get(k);
System.out.println(Thread.currentThread().getName() + "读取成功");
} finally {
readWriteLock.readLock().unlock();
}
}
public void writeTest(int k, int v) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + k);
map.put(k, v);
System.out.println(Thread.currentThread().getName() + "写入成功!");
} finally {
lock.unlock();
}
}
public void readTest (int k) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + k);
map.get(k);
System.out.println(Thread.currentThread().getName() + "读取成功");
} finally {
lock.unlock();
}
}
}
1写入1
1写入成功!
2写入2
2写入成功!
3写入3
3写入成功!
4写入4
4写入成功!
5写入5
5写入成功!
1读取1
2读取2
4读取4
1读取成功
5读取5
4读取成功
3读取3
3读取成功
2读取成功
5读取成功*
!理由上读写锁的性能优于互斥锁,但是如果读操作太短,由于读写锁本身比互斥锁复杂,可能导致性能比互斥锁差。
6.阻塞队列
6.1阻塞队列的实用场景
多线程并发处理、线程池
6.2 BlockingQueue 有四组api
方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞,等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(timenum.timeUnit) |
移出 | remove | poll | take | poll(timenum,timeUnit) |
判断队首元素 | element | peek | - | - |
6.3同步队列
同步队列 没有容量,也可以视为容量为1的队列;
进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;
put方法 和 take方法;
SynchronousQueue 和 其他的BlockingQueue 不一样 它不存储元素;
put了一个元素,就必须从里面先take出来,否则不能再put进去值!
并且SynchronousQueue 的take是使用了lock锁保证线程安全的。
7.线程池(重点)
7.1线程池的好处
-
降低资源的消耗;
-
提高响应的速度;
-
方便管理;
线程复用、可以控制最大并发数、管理线程;
7.2线程池:三大方法
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
7.3七大参数
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;
}
阿里巴巴的Java操作手册中明确说明:对于Integer.MAX_VALUE初始值较大,所以一般情况我们要使用底层的ThreadPoolExecutor来创建线程池。
7.4拒绝策略
- new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常
超出最大承载,就会抛出异常:队列容量大小+maxPoolSize
-
new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理
-
new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。
-
new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常
7.5如何设置线程池的大小
1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小
// 获取cpu 的核数
int max = Runtime.getRuntime().availableProcessors();
ExecutorService service =new ThreadPoolExecutor(
2,
max,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
2、I/O密集型:
在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。
8.四大函数式接口
Function 函数型接口
Predicate 断定型接口
Suppier 供给型接口
Consummer 消费型接口
9.Stream流式计算
/**
* Description:
* 题目要求: 用一行代码实现
* 1. Id 必须是偶数
* 2.年龄必须大于23
* 3. 用户名转为大写
* 4. 用户名倒序
* 5. 只能输出一个用户
*
* @author jiaoqianjin
* Date: 2020/8/12 14:55
**/
public class StreamDemo {
public static void main(String[] args) {
User u1 = new User(1, "a", 23);
User u2 = new User(2, "b", 23);
User u3 = new User(3, "c", 23);
User u4 = new User(6, "d", 24);
User u5 = new User(4, "e", 25);
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
// lambda、链式编程、函数式接口、流式计算
list.stream()
.filter(user -> {return user.getId()%2 == 0;})
.filter(user -> {return user.getAge() > 23;})
.map(user -> {return user.getName().toUpperCase();})
.sorted((user1, user2) -> {return user2.compareTo(user1);})
.limit(1)
.forEach(System.out::println);
}
}
10.Fork/Join(分支合并)
10.1ForkJoin 特点: 工作窃取!
实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!
10.2如何使用ForkJoin?
-
1、通过ForkJoinPool来执行
-
2、计算任务 execute(ForkJoinTask task)
-
3、计算类要去继承ForkJoinTask的实现子类recursiveTask,重写compute()方法;
ForkJoin 的计算类
package com.pt.forkjoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
public class ForkJoinTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
test2();
test3();
}
public static void test1 () {
long s = System.currentTimeMillis();
long sum = 0L;
for (long i = 0; i <= 2000_000_000; i++) {
sum += i;
}
long e = System.currentTimeMillis();
System.out.println("sum = " + sum + " time = " + (e -s));
}
/*
* 使用forkJoin框架*/
public static void test2() throws ExecutionException, InterruptedException {
long s = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> submit = forkJoinPool.submit(new ForkJoinDemo(0, 2000_000_000));
Long sum = submit.get();
long e = System.currentTimeMillis();
System.out.println("sum = " + sum + " time = " + (e -s));
}
/*
* 使用stream流计算*/
public static void test3() {
long s = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0, 2000_000_000).parallel().reduce(0, Long::sum);
long e = System.currentTimeMillis();
System.out.println("sum = " + sum + " time = " + (e -s));
}
}
class ForkJoinDemo extends RecursiveTask<Long> {
private long start;
private long end;
private long max = 1000_000L;
public ForkJoinDemo(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start < max) {
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
ForkJoinDemo fork1 = new ForkJoinDemo(start, (start + end) / 2);
fork1.fork();
ForkJoinDemo fork2 = new ForkJoinDemo((start + end) / 2 + 1, end);
fork2.fork();
return fork1.join() + fork2.join();
}
}
}
sum = 2000000001000000000 time = 731
sum = 2000000001000000000 time = 474
sum = 2000000001000000000 time = 371
10.3工作流程图
将大任务分支(fork)成小任务,递归拆分到任务足够小时计算,并合并(join)最后提交到ForkJoinTask。
10.4ForkjoinPool构造函数
ForkJoinPool有四个构造函数,其中参数最全的那个构造函数如下所示:
public ForkJoinPool(int parallelism,
ForkJoinWorkerThreadFactory factory,
UncaughtExceptionHandler handler,
boolean asyncMode)
-
parallelism:可并行级别,Fork/Join框架将依据这个并行级别的设定,决定框架内并行执行的线程数量。并行的每一个任务都会有一个线程进行处理,但是千万不要将这个属性理解成Fork/Join框架中最多存在的线程数量,也不要将这个属性和ThreadPoolExecutor线程池中的corePoolSize、maximumPoolSize属性进行比较,因为ForkJoinPool的组织结构和工作方式与后者完全不一样。而后续的讨论中,读者还可以发现Fork/Join框架中可存在的线程数量和这个参数值的关系并不是绝对的关联(有依据但并不全由它决定)。
-
factory:当Fork/Join框架创建一个新的线程时,同样会用到线程创建工厂。只不过这个线程工厂不再需要实现ThreadFactory接口,而是需要实现ForkJoinWorkerThreadFactory接口。后者是一个函数式接口,只需要实现一个名叫newThread的方法。在Fork/Join框架中有一个默认的ForkJoinWorkerThreadFactory接口实现:DefaultForkJoinWorkerThreadFactory。
-
handler:异常捕获处理器。当执行的任务中出现异常,并从任务中被抛出时,就会被handler捕获。
-
asyncMode:这个参数也非常重要,从字面意思来看是指的异步模式,它并不是说Fork/Join框架是采用同步模式还是采用异步模式工作。Fork/Join框架中为每一个独立工作的线程准备了对应的待执行任务队列,这个任务队列是使用数组进行组合的双向队列。即是说存在于队列中的待执行任务,即可以使用先进先出的工作模式,也可以使用后进先出的工作模式。
当asyncMode设置为ture的时候,队列采用先进先出方式工作;反之则是采用后进先出的方式工作,该值默认为false
10.5使用归并排序解决排序问题
package com.pt.forkjoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
public class ForkJoinTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
test2();
test3();
}
public static void test1 () {
long s = System.currentTimeMillis();
long sum = 0L;
for (long i = 0; i <= 2000_000_000; i++) {
sum += i;
}
long e = System.currentTimeMillis();
System.out.println("sum = " + sum + " time = " + (e -s));
}
/*
* 使用forkJoin框架*/
public static void test2() throws ExecutionException, InterruptedException {
long s = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> submit = forkJoinPool.submit(new ForkJoinDemo(0, 2000_000_000));
Long sum = submit.get();
long e = System.currentTimeMillis();
System.out.println("sum = " + sum + " time = " + (e -s));
}
/*
* 使用stream流计算*/
public static void test3() {
long s = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0, 2000_000_000).parallel().reduce(0, Long::sum);
long e = System.currentTimeMillis();
System.out.println("sum = " + sum + " time = " + (e -s));
}
}
class ForkJoinDemo extends RecursiveTask<Long> {
private long start;
private long end;
private long max = 1000_000L;
public ForkJoinDemo(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end - start < max) {
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
ForkJoinDemo fork1 = new ForkJoinDemo(start, (start + end) / 2);
fork1.fork();
ForkJoinDemo fork2 = new ForkJoinDemo((start + end) / 2 + 1, end);
fork2.fork();
return fork1.join() + fork2.join();
}
}
}
11.异步回调
但是我们平时都使用CompletableFuture
(1)没有返回值的runAsync异步回调
public static void main(String[] args) throws ExecutionException, InterruptedException
{
// 发起 一个 请求
System.out.println(System.currentTimeMillis());
System.out.println("---------------------");
CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
//发起一个异步任务
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+".....");
});
System.out.println(System.currentTimeMillis());
System.out.println("------------------------------");
//输出执行结果
System.out.println(future.get()); //获取执行结果
}
(2)有返回值的异步回调supplyAsync
//有返回值的异步回调
CompletableFuture<Integer> completableFuture=CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(2);
int i=1/0;
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
//success 回调
System.out.println("t=>" + t); //正常的返回结果
System.out.println("u=>" + u); //抛出异常的 错误信息
}).exceptionally((e) -> {
//error回调
System.out.println(e.getMessage());
return 404;
}).get());
whenComplete: 有两个参数,一个是t 一个是u
T:是代表的 正常返回的结果;
U:是代表的 抛出异常的错误信息;
如果发生了异常,get可以获取到exceptionally返回的值;
12.JMM(Java memory mode)
12.1对Volatile 的理解
Volatile 是 Java 虚拟机提供 轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
12.2什么是JMM?
JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存;
2、线程加锁前,必须读取主存中的最新值到工作内存中;
3、加锁和解锁是同一把锁;
线程中分为 工作内存、主内存
8种操作:
Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
JMM对这8种操作给了相应的规定:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
遇到问题:程序不知道主存中的值已经被修改过了!; 通过volatile来保证可见性
13.单例模式
13.1 饿汉式
13.2 懒汉式
13.3静态内部类
13.4枚举
14.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());
}
}
Unsafe 类
缺点:
- 循环会耗时;
- 一次性只能保证一个共享变量的原子性;
- 它会存在ABA问题
ABA问题
线程1:期望值是1,要变成2;
线程2:两个操作:
- 1、期望值是1,变成3
- 2、期望是3,变成1
所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;
带版本号的原子操作
package com.marchsoft.lockdemo;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* Description:
*
* @author jiaoqianjin
* Date: 2020/8/12 22:07
**/
public class CASDemo {
/**AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
* 正常在业务操作,这里面比较的都是一个个对象
*/
static AtomicStampedReference<Integer> atomicStampedReference = new
AtomicStampedReference<>(1, 1);
// CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
new Thread(() -> {
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("a1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 修改操作时,版本号更新 + 1
atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1);
System.out.println("a2=>" + atomicStampedReference.getStamp());
// 重新把值改回去, 版本号更新 + 1
System.out.println(atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1));
System.out.println("a3=>" + atomicStampedReference.getStamp());
}, "a").start();
// 乐观锁的原理相同!
new Thread(() -> {
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("b1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 3,
stamp, stamp + 1));
System.out.println("b2=>" + atomicStampedReference.getStamp());
}, "b").start();
}
}
15.各种锁的理解
15.1公平锁,非公平锁
- 公平锁:非常公平,不能插队,必须先来后到
- 非公平锁:非常不公平,允许插队,可以改变顺序
15.2可重入锁
15.3自旋锁
15.4死锁
死锁的检测
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
命令:jps -l
2、使用jstack
进程进程号 找到死锁信息
一般情况信息在最后:
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("b1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 3,
stamp, stamp + 1));
System.out.println("b2=>" + atomicStampedReference.getStamp());
}, "b").start();
}
}
#### 15.各种锁的理解
##### 15.1公平锁,非公平锁
1. 公平锁:非常公平,不能插队,必须先来后到
2. 非公平锁:非常不公平,允许插队,可以改变顺序
#### 15.2可重入锁
#### 
#### 15.3自旋锁
#### 15.4死锁
死锁的检测
**1、使用jps定位进程号,jdk的bin目录下: 有一个jps**
命令:`jps -l`

**2、使用`jstack` 进程进程号 找到死锁信息**

**一般情况信息在最后:**
