JUC并发编程
并行与并发
解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群
进程与线程
进程︰是内存中运行的一个应用程序,每个进程存在一个独立的内存空间,进程是系统运行程序的基本单位,进程是程序的一个执行过程
线程:是操作系统能够调度的最小单位,是进程中的一个执行单元
线程与进程的关系:一个进程至少有一个线程,可以有多个线程,每条线程并行执行不同的任务,所有的线程共享一片相同的内存空间,每个线程都有独立的栈内存来存储本地数据
简而言之∶一个程序运行后至少有一个进程,一个进程中可以包含多个线程
线程的生命周期
Java多线程中调用wait() 和 sleep()方法有什么不同?
Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。
Join方法
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
多线程的优点
(1)多线程技术使程序的响应速度更快 ,因为用户界面可以在进行其它工作的同时一直处于活动状态;
(2)占用大量处理时间的任务使用多线程可以提高CPU利用率,即占用大量处理时间的任务可以定期将处理器时间让给其它任务;
(3)多线程可以分别设置优先级以优化性能。
多线程可能会遇到的问题
- 安全问题:在单线程上正常运行的代码,多线程会有意料之外的结果
- 活跃性问题:不正确的加锁、解锁方式导致死锁或活锁
- 性能问题:多线程并发即多个线程切换运行,线程切换会有一定的消耗
死锁的四个必要条件
- 互斥条件
- 请求与保持
- 不剥夺条件
- 循环等待条件
可以通过:
- 破坏互斥条件
- 破坏请求与保持条件:一次性申请所有资源
- 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。
乐观锁和悲观锁
- 悲观锁:
悲观锁指对数据被意外修改持保守态度,依赖数据库原生支持的锁机制来保证当前事务处理的安全性,防止其他并发事务对目标数据的破坏或破坏其他并发事务数据,将在事务开始执行前或执行中申请锁定,执行完后再释放锁定。这对于长事务来讲,可能会严重影响系统的并发处理能力。 自带的数据库事务就是典型的悲观锁。- 乐观锁:
乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。
synchronized 和 Lock的区别
- Synchronized内置的Java关键字,Lock是一个Java类
- Synchronized无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- Synchronized 会自动释放锁,lock必须要手动释放锁!如果不释放锁,死锁
- Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;
- Synchronized可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置);
- Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码!
八锁现象
- synchronized锁的对象是方法的调用者
- 静态方法,类一加载它就存在,锁的是class,同一个对象只有一个Class模板
集合类不安全
-
ArrayList不安全
java.util.concurrentModificationException 并发修改异常!,并发下不安全
解决方案:
-
Listlist =new Vector<>(); 过时
-
List list=Collections.synchronizedList(new ArrayList<>())
-
List list=new CopyOnWriteArrayList<>()
//CopyOnWrite写入时复制 是COW 计算机程序设计领域的一种优化策略
-
写入时复制技术就是不同进程在访问同一资源的时候,只有更新操作,才会去复制一份新的数据并更新替换,否则都是访问同一个资源。
JDK 的 CopyOnWriteArrayList/CopyOnWriteArraySet 容器正是采用了 COW 思想,它是如何工作的呢?简单来说,就是平时查询的时候,都不需要加锁,随便访问,只有在更新的时候,才会从原来的数据复制一个副本出来,然后修改这个副本,最后把原数据替换成当前的副本。修改操作的同时,读操作不会被阻塞,而是继续读取旧的数据。这点要跟读写锁区分一下
//多个线程调用的时候,list,读取的时候是固定的,写入(覆盖)在写入的时候避免覆盖,造成数据问题!
//读写分离 MyCat
**CopyOnWriteArrayList比Vector好在哪里**:后者使用了synchronized,效率低,而前者使用的是lock锁
-
Set不安全
-
Set set=Collections.synchronizedSet(new HashSet<>())
-
Set set=new CopyOnWriteArraySet<>()
**HashSet的底层是什么?:**HashMap
public HashSet(){ map=new HashMap<>(); } //add set 本质就是map的key ,所以set是无法重复的 public boolean add(E e){ return map.put(e,PRESENT)==null; } private static final Object PRESENT=new Object();//不变值
-
-
Map不安全
- Map<String,String> map=new ConcurrentHashMap<>();
JDK1.7和1.8HashMap的区别
- 1.7是数组+链表,1.8则是数组+链表+红黑树结构,为了解决过度哈希冲突带来的长链表,会将链表转为红黑树,提高查询效率
- jdk1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而1.8则是直接调用resize()扩容
- 插入键值对的put方法的区别,1.8中会将节点插入到链表尾部,而1.7中是采用头插,头插法在并发情况下,如果插入元素的两个线程都调用了rehash方法,即扩容方法,会导致死循环的情况
Jdk1.7下HashMap的头插法问题- 扩容时1.8会保持原链表的顺序,而1.7会颠倒链表的顺序;而且1.8是在元素插入后检测是否需要扩容,1.7则是在元素插入前检测
- 扩容策略:1.7中是只要不小于阈值就直接扩容2倍;而1.8的扩容策略会更优化,当数组容量未达到64时,以2倍进行扩容,超过64之后若桶中元素个数不小于7就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表,当红黑树中元素大于等于32的时候才会再次扩容
HashMap、HashTable和ConcurrentHashMap的区别
1. HashMap
底层数组+链表实现,可以存储null键和null值,线程不安全,JDK1.8之后为了解决过度哈希冲突带来的长链表,会将链表转为红黑树,提高查询效率
初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入
插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)
当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀
2.HashTable
- 底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相关优化
- 初始size为11,扩容:newsize = olesize*2+1
3.ConcurrentHashMap
- 底层采用分段的数组+链表实现,线程安全
- 通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
- Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
- 有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
- 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
遍历Map的几种方式
- EntrySet 进行遍历,可以把 key value 同时取出
Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator();
while (entryIterator.hasNext()) {
Map.Entry<String, Integer> next = entryIterator.next();
System.out.println("key=" + next.getKey() + " value=" + next.getValue());
}
- 需要通过 key 取一次 value,效率较低,
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()){
String key = iterator.next();
System.out.println("key=" + key + " value=" + map.get(key));
}
- 需要 JDK1.8 以上,通过外层遍历 table,内层遍历链表或红黑树
map.forEach((key,value)->{
System.out.println("key=" + key + " value=" + value);
});
Callable
-
可以有返回值
-
可以跑出异常
-
方法call()
public class CallableTest{ public static void main(String[] args) throws ExecutionException, InterruptedException { //new Thread().start() Callable是怎么和Runnable联系起来的 //1.new Thread(new Runnable()).start() //2.new Thread(new FutureTask<V>()).start() //3.new Thread(new FutureTask<V>(Callable)).start() MyThread myThread=new MyThread(); FutureTask futureTask=new FutureTask(myThread);//适配类 new Thread(futureTask,"A").start(); Integer o= (Integer) futureTask.get();//获取callable的返回结果 System.out.println(o); } } class MyThread implements Callable<Integer>{ public Integer call() throws Exception { System.out.println("call()"); return 1024; } }
细节:
-
有缓存
-
结果可能需要等待,会阻塞
-
常用的辅助类
-
CountDownLatch
减法计数器
//计数器 public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { //总数是6,必须要执行任务的时候,再使用! CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = l; 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"); } }
原理:
-
countDownLatch.countDown() ;//数量-1
-
countDownLatch.await();//等待计数器归零,然后再向下执行
每次线程调用countDown()方法,数量-1,假设计数器被唤醒,就会继续执行
-
-
CyclicBarrier
加法计数器
//加法计数器 public class CyclicBarrierDemo{ public static void main(String[] args) { /*学完5门课程才能回家*/ CyclicBarrier cyclicBarrier=new CyclicBarrier(5,()->{ System.out.println("你可以回家了"); }); for (int i = 1; i <= 5; i++) { final int t=i; //lambda不能直接操作到i new Thread(()->{ System.out.println(Thread.currentThread().getName()+"写完了"+t+"门课程"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
-
Semaphore
Semaphore:信号量
public class Semaphore{ public static void main(String[] args) { //线程数量: 限流!! Semaphore semaphore=new Semaphore(3); for (int i = 0; i < 6; i++) { new Thread(()->{ try { //acquire得到 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(); } } }
原理:
- semaphore.acquire(); 获得,假如已经满了,等待被释放为止
- semaphore.release(); 释放,将当前的信号量释放+1
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数
读写锁
/*ReadWriteLock
独占锁(写锁):一次只能被一个线程占有
共享锁(读锁):多个线程可以同时占有
读-读 可以共存
读-写 不可以共存
写-写 不可以共存
* */
public class ReadWriteLock{
public static void main(String[] args) {
MyCacheLock myCacheLock=new MyCacheLock();
//写入
for (int i = 1; i <= 5; i++) {
final int t=i;
new Thread(()->{
myCacheLock.put(t+"",t+"");
},String.valueOf(i)).start();
}
//读取
for (int i = 1; i <= 5; i++) {
final int t=i;
new Thread(()->{
myCacheLock.get(t+"");
},String.valueOf(i)).start();
}
}
}
//加锁
class MyCacheLock{
private volatile Map<String,Object> map=new HashMap<>();
//读写锁:更加细粒度的控制
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//存 写入的时候,只希望同时只有一个线程写
public void put(String key,Object value){
readWriteLock.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 {
readWriteLock.writeLock().unlock();
}
}
//取 读所有人都能读
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o=map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
阻塞队列
BlockingQueue:什么情况下会使用?
多线程并发处理,线程池
四组API
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer() | put | offer(,) |
移除 | remove | poll() | take | poll(,) |
检测队列首元素 | element | peek | - | - |
-
抛出异常
/*抛出异常*/ private static void test1() { //队列大小 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("================"); System.out.println(blockingQueue.element());//查看队首元素 System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); //NoSuchElementException 抛出异常,队列为空 //System.out.println(blockingQueue.remove()); }
-
不会抛出异常
/*不抛出异常 * 有返回值*/ private static void test2(){ //队列大小 3 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("=============================="); System.out.println(blockingQueue.peek());//查看队首元素 System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); //返回null 不抛出异常 System.out.println(blockingQueue.poll()); }
-
阻塞等待
/*等待, 阻塞(一直阻塞)*/ private void test3() throws InterruptedException { //队列大小 ArrayBlockingQueue blockingQueue=new ArrayBlockingQueue<>(3); //一直阻塞 blockingQueue.put("a"); blockingQueue.put("b"); blockingQueue.put("c"); //队列没有空间,一直等待 //blockingQueue.put("d"); System.out.println("=============================="); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); System.out.println(blockingQueue.take()); //队列没有元素,一直等待,一直阻塞 System.out.println(blockingQueue.take()); }
-
超时等待
/*等待,阻塞(等待超时)*/ private void test4() throws InterruptedException { //队列大小 ArrayBlockingQueue blockingQueue=new ArrayBlockingQueue<>(3); blockingQueue.offer("a"); blockingQueue.offer("b"); blockingQueue.offer("c"); blockingQueue.offer("d",2,TimeUnit.SECONDS);//等待超过2秒就退出 System.out.println("==================="); blockingQueue.poll(); blockingQueue.poll(); blockingQueue.poll(); blockingQueue.poll(2,TimeUnit.SECONDS);//超过2秒就退出 }
SynchronousQueue同步队列
没有容量,进去一个元素,必须等待取出来之后,才能往里面放一个元素
put take
synchronousQueue 不存储元素,put了一个元素,必须从里面先take出来,否则不能put进去
线程池
线程池:三大方法、7大参数、4中拒绝策略**(比喻成银行办理业务)**
池化技术:事先准备好一些资源,有人要用,就来拿,用完之后再还回来
程序的运行,本质:占用系统的资源, 所以就要优化资源的使用=>池化技术
创建和销毁十分浪费资源
线程池的好处:
- 降低资源的消耗
- 提高响应的速度
- 方便管理
线程复用、可以控制最大并发数、管理线程
线程池的三大方法
//Executors 工具类、3大方法 public class test{ public static void main(String[] args) { //ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程 // ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程池大小 ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的,遇强则强,遇弱则弱 try { for (int i = 0; i < 100; i++) { //使用了线程池之后,使用线程池来创建线程 threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池用完,程序结束,要关闭线程 threadPool.shutdown(); } } }
7大参数
本质都是:ThreadPoolExecutor(源码)
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小 int maximumPoolSize,//最大线程池大小(最大约等于21亿,OOM溢出) 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; }
自定义线程池(7大参数)和四种拒绝策略
//Executors 工具类、3大方法 public class test{ public static void main(String[] args) { //自定义线程池!一般使用这个,因为工具类不安全 ExecutorService threadPool=new ThreadPoolExecutor( 2, 5, 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy()); //4种拒绝策略 //AbortPolicy()银行(队列)满了还有人进来,不处理这个人了,并抛出异常 //CallerRunsPolicy()哪来的去哪里 main线程处理 //DiscardPolicy()队列满了,丢掉任务,不抛出异常 //DiscardOldestPolicy()队列满了,尝试去和最早的竞争,也不会抛出异常 try { //最大承载:Deque+ max for (int i = 1; i <= 5; i++) { //使用了线程池之后,使用线程池来创建线程 threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"ok"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //线程池用完,程序结束,要关闭线程 threadPool.shutdown(); } } }
当提交一个新的任务到线程池时,处理流程如下:
1)判断核心线程池(corePool)里面还有没有空闲线程,如果还有空闲就创建新的工作线程来执行,如果没有就进入流程2;
2)判断阻塞队列(workQueue)是否已满,如果没满,就把新提交的任务放在阻塞队列里,如果满了就进入流程3;
3)判断线程池里忙碌的线程数是否达到最大(maximumPoolSize),如果还没有,就创建新的工作线程来执行,如果已经满了,就交给饱和策略处理。
池的最大线程到底该如何设置?(调优)
- CPU密集型: 4条线程同时执行 几核就定义为几,可以保证CPU的效率最高:Runtime.getRuntime().availableProcessors(),//获取CPU核数
- IO密集型:判断你的程序中十分耗IO的线程,设置比大它2倍左右
四大函数式接口
-
函数型接口
FunctionalInterface
-
断定型接口:有一个输入参数,返回值只能是 布尔值
//Predicate:断定型接口:有一个输入参数,返回值只能是布尔值 public class test{ public static void main(String[] args) { //判断字符串是否为空 /*Predicate<String> predicate=new Predicate<String>() { @Override public boolean test(String s) { return s.isEmpty(); } }; System.out.println(predicate.test(""));*/ Predicate<String> predicate1=(str)->{return str.isEmpty();}; System.out.println(predicate1.test("123")); } }
-
Consumer消费型接口:只有输入,没有返回值
//Consumer消费型接口:只有输入,没有返回值 public class test{ public static void main(String[] args) { /*Consumer<String> consumer=new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; consumer.accept("295494");*/ Consumer<String> consumer1=(str)->{ System.out.println(str+"666");}; consumer1.accept("long"); } }
-
Supplier供给型接口:没有参数,只有返回值
//Supplier供给型接口:没有参数,只有返回值 public class test{ public static void main(String[] args) { /*Supplier<Integer> supplier=new Supplier<Integer>() { @Override public Integer get() { System.out.println("get()"); return 2048; } }; System.out.println(supplier.get());*/ Supplier<Integer> supplier1=()->{ System.out.println("long:"); return 666; }; System.out.println(supplier1.get()); } }
(lambda表达式、链式编程、函数式接口、Stream流式计算)
Stream流式计算
什么是流式计算?
大数据:存储+计算
集合、MySQL本质就是存储东西的
计算都应该交给流来操作
/*现在有5个用户!筛选:|
1、ID必须是偶数
2、年龄必须大于23岁
3、用户名转为大写字母
4、用户名字母倒着排序
5、只输出一个用户!
*/
public class test{
public static void main(String[] args) {
User user1=new User(1,"a","1245",21,"男");
User user2=new User(2,"b","1245",23,"男");
User user3=new User(3,"c","1245",26,"女");
User user4=new User(4,"d","1245",22,"男");
User user5=new User(5,"e","1245",20,"男");
//集合就是存储
/*List<User>list=new ArrayList<>();
list.add(user1);
list.add(user2);
list.add(user3);
list.add(user4);
list.add(user5);*/
List<User> list= Arrays.asList(user1,user2,user3,user4,user5);
//计算交给Stream流
//Lambda表达式、链式编程、函数式接口、Stream流式计算
list.stream()
.filter(u->{return u.getUserid()%2==0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getUsername().toUpperCase();})
.sorted((u1,u2)->{return u2.compareTo(u1);})
.limit(1)
.forEach(System.out::println);
}
}
ForkJoin
ForkJoin在JDK1.7 ,并行执行任务!提高效率!。大数据量
大数据:Map Reduce(把大任务拆分为小任务)
**特点:**工作窃取(双端队列)
/**/
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;
}
//计算方法
protected Long compute(){
if((end-start)<temp){
Long sum=0L;
for (Long i=start;i<=end;i++){
sum+=i;
}
return sum;
}else{//forkjoin 递归
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();
}
}
}
public class Test{
public static void main(String[] args) {
test1();//12224
test2();//10038
test3();//153
}
//1.普通
public static void test1(){
Long sum=0L;
long start=System.currentTimeMillis();
for (Long i = 1L; i<= 1000000000; i++) {
sum+=i;
}
long end=System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
//2.使用forkjoin
public static void test2(){
long start=System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 1000000000L);
// forkJoinPool.execute(task);
ForkJoinTask<Long> submit= forkJoinPool.submit(task);
Long sum=submit.get();
long end=System.currentTimeMillis();
System.out.println("sum="+"时间:"+(end-start));
}
//3.Stream并行流
public static void test3(){
long start=System.currentTimeMillis();
long sum=LongStream.rangeClosed(0L,1000000000).parallel().reduce(0,Long::sum);
long end=System.currentTimeMillis();
System.out.println("sum="+"时间:"+(end-start));
}
}
异步回调
Future设计的初衷:对将来的某个事件的结果进行建模
/*异步调用:CompletableFuture
* 异步执行
* 异步回调
* 失败回调
* */
public class Demo{
public static void main(String[] args) {
//1.发起一个请求,没有返回值的异步回调 runAsync
CompletableFuture<Void> completableFuture=CompletableFuture.runAsync(()->{
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"runAsync=>void");
});
completableFuture.get();//获取执行结果
//2.有返回值的异步回调 supplyAsync
CompletableFuture<Integer> completableFuture=CompletableFuture.supplyAsync(()->{
return 1024;
});
System.out.println(completableFuture.whenComplete((t,u)->{
System.out.println(t);//正常的返回结果
System.out.println(u);//错误信息
}).exceptionally((e)->{
//e.printStackTrace();
System.out.println(e.getMessage());
return 404;//可以获取到错误的返回结果
}).get());
}
}
JMM
谈谈对Volatile的理解
Volatile:是java虚拟机提供的轻量级的同步机制
- 保证可见性(JMM)
- 不保证原子性
- 禁止指令重排
什么是JMM?
JMM:java内存模型,不存在的东西,概念!约定
关于JMM的一些同步的约定:
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁必须是同一把锁
线程:工作内存、主内存
8种操作
- lock(锁定)∶作用于主内存的变量,把一个变量标识为线程独占状态
- unlock(解锁)︰作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read(读取)∶作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use(使用)∶作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量
的值,就会使用到这个指令 - assign(赋值)∶作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store(存储)︰作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write(写入)∶作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
Volatile
保证可见性
public class JMMDemo{ //不加volatile程序就会死循环 //加volatile可以保证可见性 private volatile static num=0; public static void main(String[] args) {//mian线程 new Thread(()->{ while (num==0){ } }).start(); TimeUnit.SECONDS.sleep(1); num=1; System.out.println(num); } }
不保证原子性
不可分割:线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败。
//不保证原子性 public class VDemo{ private volatile static int num=0; public static void add(){ num++; } public static void main(String[] args) { for (int i = 1; i <= 20; i++) { new Thread(()->{ for (int i1 = 0; i1 < 2000; i1++) { add(); } }).start(); } while(Thread.activeCount()>2){//main() gc() Thread.yield(); } System.out.println(Thread.currentThread().getName()+""+num); } }
如果不加synchronized或者lock,如何保证原子性?
使用原子类,解决原子性
//private volatile static int num=0;改为: private volatile static AtomicInteger num=new AtomicInteger(); //num++ 改为: num.getAndIncrement()//AndIncrement + 1 方法 CAS
指令重排
什么是指令重排?
你写的程序,计算机并不是按照你写的那样去执行的
源代码—>编译器优化的重排—>指令并行也可能会重排—>内存系统也会重排—>执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性
volatile可以避免指令重排
内存屏障。是CPU指令,作用:
- 保证特定的操作的执行顺序
- 可以保证某些变量的内存可见性
单例模式
饿汉式、DCL懒汉式
饿汉式单例
//饿汉式单例 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懒汉式
//懒汉式单例 public class LazyMan{ //private static boolean wizard=false; private LazyMan(){//单例模式 构造器私有 System.out.println(Thread.currentThread().getName()+"ok"); } //试图解决反射来访问构造 /*private LazyMan(){ synchronized (LazyMan.class){ if(wizard==false){ wizard=true; }else{ throw new RuntimeException("不要试图使用反射访问单例") } if (lazyMan!=null){ } } }*/ 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.把这个对象指向这个空间 正常顺序123 有可能132 A线程 突然来个B线程,由于已经将空对象占用了空间,所以lazyMan==null会判断失败 直接执行返回,但是实际内存空间一片虚无 * */ } } } } //反射!!! public static void main(String[] args) throws NoSuchMethodException { LazyMan instance = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//空参构造器 declaredConstructor.setAccessible(true);//无视私有构造器 LazyMan instance2 = declaredConstructor.newInstance(); System.out.println(instance); System.out.println(instance2); }
3.静态内部类
public class Holder{ private Holder(){ } public static Holder getInstance(){ return InnerClass.HOLDER; } public static class InnerClass{ private static final Holder HOLDER =new Holder(); } }
单例不安全,因为有反射
====>枚举
//枚举Enum是一个什么 //本身也是一个class类 public enum EnumSingle{ INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) { EnumSingle instance1=EnumSingle.INSTANCE; Constuctor<EnumSingle> declaredConstructor=EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingle instance2=declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } } //结果:NoSuchMethodException: com.qst.single.EnumSingle.<init> //枚举类型的最终反编译源码
深入理解CAS
什么是CAS?
CompareAndSet:比较并交换!
public final boolean compareAndSet(int expect,int update)
//期望 更新 如果期望的值达到了,那么久更新,否则就不更新
CAS是CPU的并发原语
public class CASDemo{ //CAS:CompareAndSet:比较并交换! public static void main(String[] args) { AtomicInteger atomicInteger=new AtomicInteger(2020); //期望 更新 //public final boolean compareAndSet(int expect,int update) //如果期望的值达到了,那么久更新,否则就不更新 System.out.println(atomicInteger.compareAndSet(2020,2022)); System.out.println(atomicInteger.get()); atomicInteger.getAndIncrement(); System.out.println(atomicInteger.compareAndSet(2020,2021)); System.out.println(atomicInteger.get()); } }
Unsafe 类
CAS:ABA问题(狸猫换太子)
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,2022)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2022,2020)); System.out.println(atomicInteger.get()); //=================期望的线程========================= System.out.println(atomicInteger.compareAndSet(2020,6666)); System.out.println(atomicInteger.get()); } }
原子引用
带版本号的原子操作
解决ABA问题
public class CASDemo{ //CAS:CompareAndSet:比较并交换! public static void main(String[] args) { //AtomicInteger atomicInteger=new AtomicInteger(2020); final AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(6,1); new Thread(()->{ int stamp = atomicStampedReference.getStamp(); //获得目前的版本号 System.out.println("a1=>"+stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(6,8 , atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println("a2=>"+atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(8,6 , 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(); } atomicStampedReference.compareAndSet(6,66, stamp,stamp+1); //atomicStampedReference.getStamp()获取当前最新的版本号 System.out.println("b2=>"+atomicStampedReference.getStamp()); },"B").start(); } }
各种锁的理解
1、公平锁、非公平锁
公平锁:非常公平,不能够插队,必须先来后到
非公平锁:非常不公平,可以插队(默认都是非公平)
//默认非公平锁
Lock lock=new ReentrantLock();
public ReentrantLock() {
sync = new ReentrantLock.NonfairSync();
}
//公平锁
Lock lock=new ReentrantLock(true);
public ReentrantLock(boolean fair) {
sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
}
2、可重入锁
可重入锁(递归锁)
拿到了外面的锁,就可以拿到里面的锁,自动获得
- synchronized
public class Demo01{
public static void main(String[] args){
final Phone phone=new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"发信息");
call();//这里也有锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
}
- Lock
public class Demo02{
public static void main(String[] args){
final Phone phone=new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
Lock lock=new ReentrantLock();
public void sms(){
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();
}
}
}
3、自旋锁
自定义一个锁
public class SpinlockDemo{
AtomicReference<Thread> atomicReference=new AtomicReference<Thread>();
//加锁
public void myLock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"===>mylock");
//自旋锁
while(!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void myUnLock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"===>myUnlock");
atomicReference.compareAndSet(thread,null);
}
}
测试
public class TestSpinLock{
public static void main(String[] args) {
/*ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
reentrantLock.unlock();*/
//底层使用的自旋锁,CAS实现
final SpinlockDemo spinlockDemo=new SpinlockDemo();
new Thread(()->{
spinlockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlockDemo.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
spinlockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinlockDemo.myUnLock();
}
},"T2").start();
}
}
4、死锁
public class DeadLockDemo{
public static void main(String[] args) {
String lockA="lockA";
String lockB="lockB";
new Thread(new MyThread(lockA,lockB),"T1").start();
new Thread(new MyThread(lockB,lockA),"T2").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"want get"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"want get"+lockA);
}
}
}
}
解决问题
使用jps -l定位进程号
- 使用jstack进程号查看进行信息,找到死锁问题