JUC并发编程
1、什么是JUC
java.util
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks
JUC就是java的工具包
业务:普通的线程代码 Thread
Runnable 没有返回值,效率相比Callable更低
2、线程和进程
进程:应用程序:如QQ
一个进程往往可以包括多个线程,至少包括一个
Java默认有两个线程:主线程,守护线程(GC)
java不能开启线程,通过本地方法进行调用C++的方法
并发和并行
并发(多线程操作同一个资源)
- CPU一核,模拟多条线程,天下武功,唯快不破,快速交替
并行(多个人一起行走)
- CPU多核,多条线程同时执行;线程池
并发编程的本质:充分利用CPU的资源
线程的状态
新生状态:new
运行状态:blocket
等待:watting
超时等待:time_watting
终止:terminated
wait/sleep的区别
1、来自不同的类
wait=>Object
sleep=>Thread
企业当中一般用TimeUnit
2、关于锁的释放
wait,会释放锁
sleep,不会释放锁
3、使用的范围不同
wait,必须在同步代码块中
sleep,可以在任何地方睡
4、是否需要捕获异常
wait,不需要捕获一场
sleep,必须要捕获异常
3、Lock锁(重点)
传统synchronized
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for (int i =0;i<40;i++){
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i =0;i<40;i++){
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i =0;i<40;i++){
ticket.sale();
}
},"C").start();
}
}
class Ticket{
private int number = 100;
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName() + "抢到第" + (number--) + "张票,剩余" + number + "张票");
}
}
}
Lock接口
加锁:.lock
解锁:.unlock
可重入锁:ReentrantLock,ReentrantReadWriteLock.ReadLock(读锁),ReentrantReadWriteLock.WriteLock(写锁)
公平锁:十分公平,先来后到
非公平锁:十分不公平,可以插队(默认)
使用步骤:
- new ReentrantLock();
- lock.lock();
- finally=>lock.unlock();
class Ticket2{
private int number = 100;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock();
try {
if(number>0){
System.out.println(Thread.currentThread().getName() + "抢到第" + (number--) + "张票,剩余" + number + "张票");
}
}catch (Exception e){
}finally {
lock.unlock();
}
}
}
synchronized和Lock的区别
- synchronized是一个内置的java关键字,Lock是一个java类
- synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
- synchronized会自动释放锁,Lock锁必须要手动释放锁!如果不释放会发生死锁
- synchronized 线程1阻塞,线程2会继续等,Lock锁不一定会等待下去
- synchronized 可重入锁,不可以中断的,非公平;Lock 可重入锁,可以判断锁,可以自己设置公平或不公平
- synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码
锁是什么,如何判断锁的是谁
4、生产者和消费者问题
生产者和消费者问题synchronized版本
class Data{
private int number = 0;
public synchronized void increment() throws InterruptedException {
if(number!=0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if(number==0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+number);
this.notifyAll();
}
}
如果线程更多,会发生问题,会发生 虚假唤醒
if判断需更换为while循环
class Data{
private int number = 0;
public synchronized void increment() throws InterruptedException {
while(number!=0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while(number==0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+number);
this.notifyAll();
}
}
JUC版本的生产者和消费者
Lock下面的await方法相当于synchronized的wait方法
Lock下面的signal方法相当于synchronized的notify方法
class Data2{
private int number = 0;
Lock lock = new ReentrantLock();
public synchronized void increment() throws InterruptedException {
lock.lock();
Condition condition = lock.newCondition();
try {
while(number!=0){
condition.await();
}
condition.signalAll();
number++;
System.out.println(Thread.currentThread().getName()+number);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public synchronized void decrement(){
lock.lock();
Condition condition = lock.newCondition();
try {
while(number==0){
condition.await();
}
condition.signalAll();
number--;
System.out.println(Thread.currentThread().getName()+number);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition精准的通知和唤醒线程
class Data3 {
private Lock lock = new ReentrantLock();
private int number = 1;
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void printA() {
lock.lock();
try {
while (number != 1) {
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAAAAAAA");
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (number != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBBBBBBB");
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (number != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "=>CCCCCCCC");
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5、8锁现象
判断锁的是谁!永远知道什么锁
6、集合类不安全
List不安全
ArrayList在多线程下不安全,可能会出现ConcurrentModificationException
异常(并发修改异常)
public class CurrentArrayList1 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for(int i =1;i<=10;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
解决方案:
//jdk1.0就有的,效率低
List<String> list1 = new Vector<>();
List<String> list1 = Collections.synchronizedList(new ArrayList<>());
//写入时复制 COW 计算机程序设计领域的一种优化策略
//多个线程调用的时候 list在读取的时候是固定的,写入(覆盖)
//相当于在写入的时候避免覆盖,造成数据问题
List<String> list1 = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList
比Vector
效率更低,因为Vertor
使用了synchronized
Set不安全
与ArrayList同理,同样会出现并发修改异常
public class CurrentHashSet1 {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
for (int i = 0;i<100;i++){
new Thread(()->{
set1.add(UUID.randomUUID().toString().substring(0,3));
System.out.println(set1);
},String.valueOf(i)).start();
}
}
}
解决方案:
Set<String> set1 = Collections.synchronizedSet(new HashSet<>());
Set<String> set1 = new CopyOnWriteSet<>();
HashSet底层是什么
public HashSet() {
map = new HashMap<>();
}
add set 本质就是map key无法重复的!
Map不安全
HashMap底层加载因子0.75,初始大小为1 << 4(16),最大容量1 << 30
多线程下依然不安全,使用ConcurrentHashMap();
7、Callable(简单)
Callable
接口类似于Runnable
,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,A Runnable
不返回结果,也不能抛出被检查的异常。
- 可以有返回值
- 可以抛出异常
- 方法不同
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread("A").start();
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<>()).start();
// new Thread(new FutureTask<>(Callable)).start();
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);//适配类
new Thread(futureTask,"B").start();
Integer o = (Integer)futureTask.get();//可能会产生阻塞,放到最后一行,或者使用异步通信
System.out.println(o);
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("Call");
return 1024;
}
}
细节:
- 有缓存
- 结果可能需要等待,会阻塞
8、常用的辅助类
1、CountDownLatch
减法计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
for(int i=1;i<=10;i++) {
countDownLatch.countDown();// 数量减1
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
}, String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归零,再执行下面操作
System.out.println("Close the door");
}
}
- countDownLatch.countDown();// 数量减1
- countDownLatch.await();等待计数器归零,再执行下面操作
每次有线程调用countDown()
方法时数量减1,假设数据变为0,await();
方法就会被唤醒,继续执行!
2、SyclicBarrier
加法计数器
public class CycliBarrierDemo {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,new Thread(()->{
System.out.println("召唤神龙");
}));
for(int i=1;i<=7;i++){
new Thread(()->{
System.out.println("第"+Thread.currentThread().getName()+"颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
3、Semaphore
Semaphore:信号量
只允许指定数量的线程(限流)
public class SemaphoreDemo {
public static void main(String[] args) {
//permits:线程数量
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//acquire :得到
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 {
//release :释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
acquire(); 获得,假设已经满了进入等待状态,知道有线程被释放才进行获取(阻塞)
release(); 释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!
9、读写锁
ReentrantReadWriteLock:可以被多个线程同时读,只能有一个线程写
/**
* 独占锁(写锁)只能被一个线程占有
* 共享锁(读锁)多个线程可以同时占有
* ReadWriteLock
* 读 - 读 可以共存
* 读 - 写 不能共存
* 写 - 写 不能共存
* @author lee
* @date 2020/9/27 - 2:17 下午
*/
public class ReentrantReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i < 10; i++) {
final int temp = i;
new Thread(()->{
myCache.get(temp + "");
},String.valueOf(i)).start();
}
for (int i = 1; i < 10; i++) {
final int temp = i;
new Thread(()->{
myCache.set(temp + "","");
},String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile Map<String,String> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入进入");
map.get(key);
System.out.println(Thread.currentThread().getName() + "写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
public void set(String key,String value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取进入");
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "读取完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
}
10、阻塞队列
BlockingQueue和List、Set同样都是Conllection下的接口
什么情况下会使用阻塞队列:多线程并发处理,线程池
学会使用队列
添加、移除
四组API
- 抛出异常
- 不会抛出异常
- 阻塞等待
- 超时等待
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞 等待 | 超时等待 |
添加 | add | offer() | put() | offer(,) |
移除 | remove | poll() | take() | poll(,) |
检测队首元素 | element | peak() |
抛出异常
public static void test01(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//IllegalStateException: Queue full 会抛出异常:队列已满
System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//NoSuchElementException 没有元素
System.out.println(blockingQueue.remove());
}
有返回值,不抛出异常
public static void test02(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//返回false,不抛出异常
System.out.println(blockingQueue.offer("d"));
//查看队首元素
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//返回空,不抛出异常
System.out.println(blockingQueue.poll());
}
阻塞等待
public static void test03() 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 test04() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a",2, TimeUnit.SECONDS);
blockingQueue.offer("b",2, TimeUnit.SECONDS);
blockingQueue.offer("c",2, TimeUnit.SECONDS);
//如果等待两秒无法加入,直接跳过
blockingQueue.offer("d",2, TimeUnit.SECONDS);
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
//如果等待两秒无法获取,直接跳过
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}
SynchronousQueue(同步队列)
没有容量
进去一个元素,必须等待取出来之后,才能再往里面放一个元素
同步队列实现:
public class SynchronousQueueDemo {
public static void main(String[] args) {
//SynchronousQueue和其他的BlockingQueue不一样,它不存储元素,只要put一个元素必须先取出来,否则不能再put
SynchronousQueue synchronousQueue = new SynchronousQueue();//同步队列
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "put 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "put 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() +"==" +synchronousQueue.take());
System.out.println(Thread.currentThread().getName() +"==" +synchronousQueue.take());
System.out.println(Thread.currentThread().getName() +"==" +synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
11、线程池(重点)
线程池:三大方法、七大参数、4种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!优化资源的使用!=》池化技术
线程池、连接池、内存池、对象池、、、、、、
池化技术:实现准备好一些资源,有人要用,就来池子里面拿,用完还回来
线程池的好处:
- 降低资源的消耗
- 提高响应的速度
- 方便管理
线程服用、可以控制最大并发数、管理线程
线程池三大方法
/**
* Executor 工具类,三大方法
* 使用了线程池之后,要使用线程池创建线程
*
* @author lee
* @date 2020/9/28 - 7:14 下午
*/
public class poolDemo01 {
public static void main(String[] args) {
// ExecutorService executorService = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService executorService = Executors.newFixedThreadPool(6);//创建一个固定的线程池的大小
ExecutorService executorService = Executors.newCachedThreadPool();//可伸缩的线程池
try {
for (int i = 0; i < 100; i++) {
//使用了线程池之后,要使用线程池创建线程
executorService.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池用完要关闭
executorService.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.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;
}
- int corePoolSize,//核心线程池大小
- int maximumPoolSize,//最大线程池大小
- long keepAliveTime,//超时了没有人调用就会释放
- TimeUnit unit,//超时单位
- BlockingQueue workQueue,//阻塞队列
- ThreadFactory threadFactory//线程工厂,创建线程的,一般不用动
- RejectedExecutionHandler handler//拒绝策略
手动创建一个线程池
public class poolDemo02 {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2, //默认容量
5,//最大可处理并发量
3, //3秒没有使用,就关闭开放的扩展线程池
TimeUnit.SECONDS,//单位 ,秒
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 1; i <= 8; i++) {
//使用线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "ok");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//使用完毕后关闭线程池
threadPool.shutdown();
}
}
}
四种拒绝策略
/**
* ThreadPoolExecutor.AbortPolicy() 满了,还有人进来,不处理,直接报异常
* ThreadPoolExecutor.CallerRunsPolicy() 哪来的回哪去
* ThreadPoolExecutor.DiscardOldestPolicy() 队列满了,尝试丢掉最早的任务,不会抛出异常
* ThreadPoolExecutor.DiscardPolicy() 队列满了,丢掉任务,不会抛出异常
* @author lee
* @date 2020/9/30 - 8:31 上午
*/
小结
最大线程如何定义
- CPU密集型,几核就是几,可以保持CPU的效率最高
- IO密集型 判断你程序中十分耗IO的线程,设置大于这个线程的数量
12、四大函数式接口(必须掌握)
四大函数式接口:Function、Predicate、Consumer、Supplier
新时代的程序员:lambda表达式、链式编程、Stream流式计算
函数式接口:
只有一个方法的接口,如:Runnable,简化编程模型,在新版本的框架底层大量应用,foreach(消费者类的函数式接口)
Function 函数型接口
有一个输出参数,有一个输出参数
只要是函数式接口 就可以用lambda表达式简化
代码测试:
//普通写法
Function function = new Function<String,String>(){
@Override
public String apply(String o) {
return o;
}
};
System.out.println(function.apply("asd"));
//------------------------------------------
//lambda表达式写法
Function<String,String> function1 = (str)->{return str;};
System.out.println(function1.apply("aaa"));
Predicate 断定型接口
有一个输入参数,返回值只能是boolean值
代码测试:
//普通写法
Predicate<String> stringPredicate = new Predicate<String>() {
@Override
public boolean test(String o) {
return o.isEmpty();
}
};
System.out.println(stringPredicate.test("s"));
//lambda表达式写法
Predicate<String> stringPredicate1 = (str)->{
return str.isEmpty();
};
System.out.println(stringPredicate1.test(""));
Consumer 消费型接口
只有输入,没有返回值
代码测试:
//普通写法
Consumer<String> objectConsumer = new Consumer<String>() {
@Override
public void accept(String o) {
System.out.println(o);
}
};
objectConsumer.accept("hello world");
//lambda表达式写法
Consumer<String> objectConsumer1 = (str)->{ System.out.println(str);};
objectConsumer1.accept("hello world hello");
Supplier 供给型接口
没有参数,只有返回值
代码测试:
//普通写法
Supplier<Integer> objectSupplier = new Supplier<Integer>() {
@Override
public Integer get() {
System.out.println("get()");
return 1024;
}
};
System.out.println(objectSupplier.get());
//lambda表达式写法
Supplier<Integer> objectSupplier1 = ()->{ System.out.println("get()"); return 1024;};
System.out.println(objectSupplier1.get());
13、Stream流式计算
Java.util.stream
什么是Stream流式计算
大数据:存储+计算
存储:集合、Mysql 本质是用来存东西的
计算都应该交给流来做
代码测试:
public static void main(String[] args) {
User user1 = new User(1,"a",10);
User user2 = new User(2,"b",10);
User user3 = new User(3,"c",10);
User user4 = new User(4,"d",25);
User user5 = new User(5,"e",22);
User user6 = new User(6,"f",30);
//集合就是存储数据的
List<User> list = Arrays.asList(user1, user2, user3, user4, user5, user6);
//计算交给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((u1,u2)->{return u2.compareTo(u1);})
.limit(1)
.forEach(System.out::println);
;
}
14、ForkJoin
什么是ForkJoin
ForkJoin在JDK1.7,执行并行任务!提高效率,大数据量!
大数据:Map Reduce(把大任务拆分为小任务)
ForkJoin特点:工作窃取
这里面维护的都是双端队列,B处理完会帮A处理
ForkJoin
代码实现:
/**
* @author lee
* @date 2020/9/30 - 2:29 下午
*/
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) {
//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();
} else {
Long sum = 0L;
for (Long i = start; i <= end; i++) {
sum += i;
}
return sum;
}
}
}
测试:
//普通程序员
//结果:500000000500000000
//时间:439ms
public static void test01(){
long sum = 0;
long start = System.currentTimeMillis();
for (long i = 0; i <= 10_0000_0000; i++) {
sum += i;
}
System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println("时间:" + (end-start) + "ms");
}
//使用forkJoin
//结果:500000000500000000
//时间:4721ms
public static void test02() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinDemo task = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get();
System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println("时间:" + (end-start) + "ms");
}
//Stream并行流计算
//结果:500000000500000000
//时间:359ms
public static void test03(){
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println("时间:" + (end-start) + "ms");
}
15、异步回调*
future设计的初衷:对将来的某个事件的结果进行建模
16、JMM(Java内存模型)
JMM:Java Memory Model 是一个概念!约定
关于JMM的一些同步的约定:
- 线程解锁前 必须把共享变量立刻写回主存!
- 线程加锁前 必须读取主存中的最新值到工作内存中!
- 加锁和解锁是同一把锁
线程 工作内存、主内存
8种操作:
- lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
- read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
- load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
- use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
- assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
- store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
- write(写入):作用于主内存,它把store传送值放到主内存中的变量中。
- unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:
(1)不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。
(2)不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
(3)不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
(4)一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
(5)一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
(6)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
(7)如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
(8)对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。
存在问题:线程B修改了内存,线程A不能及时的可见
17、volatile
volatile是java虚拟机中提供轻量级的同步机制
- 保证可见性
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while(num == 0){
System.out.println(num);
}
}).start();
TimeUnit.SECONDS.sleep(2);
num = 1;
System.out.println(num);
}
- 不保证原子性
原子性:线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败
/**
* volatile不保证原子性
* @author lee
* @date 2020/10/1 - 3:18 下午
*/
public class volatileDemo02 {
public volatile static int num;
public static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
System.out.println(num);
}
}
通过反编译可知,在num++操作时存在3步,获得值、+1、写回,所以这个操作不是一个原子性操作,而volatile不保证原子性,一般使用原子类解决原子性问题
**原子类:**原子类的底层都和操作系统有关,比synchronized和Lock高效很多
//使用原子类AtomicInteger
public volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
// num++;
num.getAndIncrement(); //执行AtomicInteger + 1 操作
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
System.out.println(num);
}
- 禁止指令重排
指令重排:你写的程序,计算机并不是按照你写的那样去执行的
源代码–>编译器优化的重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,回考虑到数据之间的依赖性
在多线程下可能会出现问题
内存屏障。CPU指令。作用:
- 保证特定的操作的执行顺序
- 可以保证某些变量的内存可见性
加volatile会在volatile方法的上面和下面都加上一层内存屏障,来禁止指令重排
18、彻底玩转单例模式
饿汉式 DCL懒汉式 枚举
19、深入理解CAS
什么是CAS: Comapre and Swap(比较并交换)CAS是CPU的并发原语
Java无法操作内存,Java可以通过native方法调用C++,C++可以操作内存
大厂必须要深入研究底层!有所突破
**getAndIncrement方法底层:**自旋锁
CAS:比较当前内存中的值和主内存中的值,如果这个值是期望的,那么执行操作!如果不是就一直循环
CAS缺点:
- 由于底层是自旋锁,循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
CAS:ABA问题(狸猫换太子)
A在操作数据i时,B在中间将i改变了值并且又改了回来
/**
* @author lee
* @date 2020/10/1 - 5:13 下午
*/
public class CASDemo02 {
//CAS
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//乐观锁:
//public final boolean compareAndSet(int expect, int update)
//两个参数,期望,更新,如果期望值达到了,就更新,返回true,否则返回false
//=====================捣乱的线程========================
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println("2->" +atomicInteger);
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println("3->" +atomicInteger);
//=====================期望的线程========================
System.out.println(atomicInteger.compareAndSet(2020, 2333));
System.out.println("4->" +atomicInteger);
}
}
20、原子引用
解决ABA问题,引入原子引用,对应思想–乐观锁
带版本号的原子操作
大坑:Integer使用了对象缓存机制,默认范围时-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间
/**
* @author lee
* @date 2020/10/1 - 5:13 下午
*/
public class CASDemo03 {
//CAS
public static void main(String[] args) {
// AtomicInteger atomicInteger = new AtomicInteger(2020);
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(100,1);
int stamp = atomicInteger.getStamp();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(100, 110, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println(atomicInteger.getStamp());
System.out.println(atomicInteger.compareAndSet(110, 100, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println(atomicInteger.getStamp());
},"a").start();
//乐观锁的原理相同
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(100, 110, stamp, stamp + 1));
System.out.println(atomicInteger.getStamp());
},"b").start();
}
}
各种锁
公平锁:非常公平,先来后到,不能插队
非公平锁:非常不公平,可以插队(默认都是非公平的)
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可重入锁
可重入锁:递归锁,拿到了外面的锁自动获得里面的锁
Synchronized:
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.smsg();
}).start();
new Thread(()->{
phone.smsg();
}).start();
}
}
class Phone{
public synchronized void smsg(){//一把锁
System.out.println(Thread.currentThread().getName() + "发短信");
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName() + "打电话");
}
}
Lock:
/**
* @author lee
* @date 2020/10/2 - 10:02 上午
*/
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.smsg();
}).start();
new Thread(()->{
phone.smsg();
}).start();
}
}
class Phone2{
Lock lock = new ReentrantLock();
public void smsg(){
lock.lock();//两把锁
try {
System.out.println(Thread.currentThread().getName() + "发短信");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "打电话");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
自旋锁
spinLock
自定义一个自旋锁:
/**
* 自旋锁
* @author lee
* @date 2020/10/2 - 10:26 上午
*/
public class SpinLockDemo01 {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void lock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "加锁");
while(!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void unlock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "解锁");
atomicReference.compareAndSet(thread,null);
}
}
测试:
/**
* 测试自旋锁
* @author lee
* @date 2020/10/2 - 12:03 下午
*/
public class TestSpinLock {
public static void main(String[] args) {
//底层使用的自旋锁CAS
SpinLockDemo01 lock = new SpinLockDemo01();
new Thread(() -> {
lock.lock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "T1").start();
new Thread(() -> {
lock.lock();
try {
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "T2").start();
}
}
死锁
什么是死锁:A持有a锁,B持有b锁,A和B都试图获取对方的锁
代码实现死锁:
/**
* 死锁
* @author lee
* @date 2020/10/2 - 12:20 下午
*/
public class DeadLockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA,lockB)).start();
new Thread(new MyThread(lockB,lockA)).start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName() +"获取到" +lockA +"想获取"+ lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName() +"获取到" +lockB +"想获取"+ lockA);
}
}
}
}
解决死锁:
- 使用jps定位进程号
- 使用
jstack + 进程号
查看进程信息
面试或工作中排查问题:
- 日志
- 堆栈信息