- 什么是JUC
JUC是java中java.util.concurrent工具类的缩写
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
线程:一个进程中至少有一个线程。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
并发:单核,多线程操作一个资源
并行:多核,多线程可以同时运行
并发编程的本质:充分利用CPU资源
wait:会释放锁,有异常
sleep:不会释放锁,有异常
Lock锁(重点):
什么是锁:
- synchronize锁的对象是方法的调用者,先来后到
- 普通方法不受锁的限制
- 锁之间不关联
- static静态方法,class模板,synchronize锁的是class
- 先执行普通锁,再执行static的锁
- 锁new为具体对象,锁static为class模板
synchronize:锁定资源
Lock:
公平锁:先来后到
非公平锁:可以插队
synchronize与lock的区别:
- synchronize是内置关键字,Lock是类
- synchronize无法判断取锁的状态,Lock可以判断是否获取锁
- synchronize自动释放锁,Lock要手动释放
- synchronize如果有线程阻塞,其它线程只能等待。Lock可以不用等待
- synchronize 可重入锁,不可以中断,非公平锁。Lock可重入锁,可以判断锁,自己设置锁
- synchronize用于少量代码,Lock用于大量代码
synchronize代码块里面为什么要用while而不是if
我们知道当线程执行到wait()方法时,会一直进行等待,直到被幻醒notify()或notifyAll()才会继续执行代码;如果用if的话,会不执行if中的条件,因为在执行wait()时已经执行了条件语句;而用while的话会继续执行条件语句,从而不会出现虚假唤醒的问题。
JDK文档:
在synchronize使用if
在synchronize使用while
Lock+Condition实现PC
package com.lyt; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @description :说明 */ public class LockPCTest { public static void main(String[] args) { final PC pc = new PC(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { pc.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start();new Thread(()->{ for (int i = 0; i < 10; i++) { try { pc.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start();new Thread(()->{ for (int i = 0; i < 10; i++) { try { pc.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { pc.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"D").start(); } } class LockPC { private int number=0; Lock lock=new ReentrantLock(); Condition condition=lock.newCondition(); //生产 public void increment() throws InterruptedException { lock.lock(); try { while (number!=0){ condition.await(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } number++; System.out.println("当前线程"+Thread.currentThread().getName()+":"+number); condition.signalAll();//通知其它线程已生产 } //消费 public void decrement() throws InterruptedException { lock.lock(); try { while (number==0){ condition.await();//等待 } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } number--; System.out.println("当前线程"+Thread.currentThread().getName()+":"+number); condition.signalAll();//通知其它线程已消费 } }
condition比synchronize的优势:能够精确的唤醒线程
利用Condition使多线程按照自己定义的顺序执行
class LockPC1 { private int number = 1; Lock lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); Condition condition3 = lock.newCondition(); public void A() throws InterruptedException { lock.lock(); try { while (number != 1) { condition1.await(); } System.out.println("AAAAAAAAAAAAA " + Thread.currentThread().getName() + ":" + number); number=2; condition2.signal();//通知线程2 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void B() throws InterruptedException { lock.lock(); try { while (number != 2) { condition2.await();//等待 } System.out.println("BBBBBBBBBBBBB " + Thread.currentThread().getName() + ":" + number); number=3; condition3.signal();//通知线程3 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void C() throws InterruptedException { lock.lock(); try { while (number != 3) { condition3.await();//等待 } System.out.println("CCCCCCCCCCCCCCCCC " + Thread.currentThread().getName() + ":" + number); number=1; condition1.signal();//通知线程1 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
集合不安全:
多线程操作集合可能存在不安全,如ArrayList,HashMap等
安全集合有哪些:
(concurrent下的集合使用Lock锁,vector使用synchronize锁,使用前者的性能高)
HashSet用的是HashMap,
concurrentHashMap的原理推荐文章:https://www.jianshu.com/p/865c813f2726
Callable
靠FutureTask类来适配Runable接口,来启动线程
可以有返回值
可以抛出异常
方法不同,run()/call()
辅助类
countDownLatch(减法计数器)等线程执行完在执行await
CyclicBarrier(加法计数器)等线程执行完在执行await
Semaphore(信号量)等线程执行完在执行await
Semaphore.acquire()获取,已满则等待,直到释放
Semaphore.release()释放,信号量+1,唤醒其他线程
作用:限流,多线程操作互斥资源
读写锁:
ReaderWriteLock
class DataTest{ private Map map=new HashMap(); private ReadWriteLock readWriteLock=new ReentrantReadWriteLock(); public void write(String a,String b){ try { //只能由一个线程写入完成,才能执行其他线程(独占锁)写锁 readWriteLock.writeLock().lock(); System.out.println(Thread.currentThread().getName()+"写入前"); map.put(a,b); System.out.println(Thread.currentThread().getName()+"写入后"); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.writeLock().unlock(); } } public void reader(String a){ try { //加不加读取锁都一样,因为是多线程共享(共享锁)读锁 // readWriteLock.readLock().lock(); System.out.println(Thread.currentThread().getName()+"读取前"); map.get(a); System.out.println(Thread.currentThread().getName()+"读取后"); } catch (Exception e) { e.printStackTrace(); } finally { // readWriteLock.readLock().unlock(); } } }
阻塞队列
阻塞
队列(FIFO)
BlockingQueue
四组API
synchronizeQueue
最多放一个元素,也就是说进去一个,等取出来在进去
线程池(重点)
池化技术
不用用Executors去创建线程池(不安全),用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.AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
最大线程如何去定义(调优)?
CPU密集型,几核的CPU就定义多少,效率最高
IO密集型,根据程序的耗IO的线程,大于该线程就好啦
最大线程池的触发:线程数大于(maximumPoolSize+workQueue),触发最大线程池后会执行拒绝策略
自定义创建线程池:
public class PoolTest { public static void main(String[] args) { ExecutorService executorService=new ThreadPoolExecutor( 2, Runtime.getRuntime().availableProcessors(),//获取CPU的核数 3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy() ); try { for (int i = 1; i <= 4; i++) { executorService.execute(()->{ System.out.println(Thread.currentThread().getName()); }); } } catch (Exception e) { e.printStackTrace(); } finally { executorService.shutdown(); } } }
四大函数式接口(重点)
四大知识点:lambda表达式“()->{}”、链式编程“xxx().xxx().xxx()”、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口,如Runnable接口,框架大量使用
Function函数式接口(用创造于工具类)
有一个输入T,有一个返回R
lambda表达式使用Function函数接口:
Predicate函数式接口(用于写判断工具,比如判断是否为空)
有一个参数,返回一个Boolean类型的值
lambda表达式使用Predicate函数接口:
Consumer(消费)函数式接口
有一个输入,没有返回值
Supplier(供给)函数式接口
没有输入,只有返回值
lambda表达式使用Consumer、Supplier函数接口:
Stream流式计算
什么是Stream流式计算
计算应该交给Stream流(java.util.Stream)
使用Stream的推荐文章:https://www.cnblogs.com/balloon72/p/13177916.html
ForkJoin(分之合并),思想拆分=》分治法
jdk1.7后的,并行执行任务,提高工作效率
特点:工作窃取,简单说线程自己的任务完成了,就会窃取别线程的任务,从而提高效率
异步回调
future的实现类CompletableFuture类(类似ajax)
一个线程向另外一个线程发出异步请求
JMM
volatile是java虚拟机提供轻量级的同步机制
- 保证可见性=》加volatile
- 不保证原子性=》用java.util.concurrent.atomic包解决
- 禁止指令重排(单例模式中使用)
什么是JMM(java内存模型):(推荐文章:https://www.jianshu.com/p/8a58d8335270)
内存交互操作有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的约定
- 线程解锁前:必须把共存变量立刻刷回内存
- 线程加锁前:必须读取主存中的最新值到工作区中
- 加锁、解锁是同一把锁
指令重排(你写的程序并不是按照你的顺序执行的)
源代码==》编译器优化重排序==》指令级并行重排序==》内存系统重排序==》执行指令
单例模式(要构造器私有private)
饿汉式单例模式(直接new)
懒汉式单例模式(要用才new)
懒汉式单例模式(单锁)(先检测是否创建再要用才new)
懒汉式单例模式(DCL=双重检查)(先检测是否创建再判断是否存在实例,再要用才new)
不是一个原子性操作,也就是说不是按照如下123,的顺序执行可能按照321顺序执行,所以在该方法中加关键字volatile防止指令重排
- 分配内存空间
- 执行构造方法,初始化对象
- 把这个对象这个空间
反射可以忽视私有的构造器,所以可以破坏有私有构造器的单例模式
枚举(enum)的单例模式解决java反射的破坏单例模式
深入CAS(compareAndSet=比较并交换):比较当前工作中的值和主内存的值,如果这个值是期望的,则执行操作,如果不是就一直循环,因为他有自旋锁
在AtomicInteger类里,如果达到了expect,则update,否则不更新,CAS是CPU的并发原语
缺点:
- 循环耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题(狸猫换太子)(解决:引用原子引用AtomicReference类,加乐观锁)
Unsafe类直接调用本地接口操作内存
锁
可重锁(递归锁)(简单理解锁中锁)
多线程,全部线程执行完a,再执行b
自旋锁
死锁:两把锁相互占用对方所需的资源并且不释放该资源
检测死锁:jps -l
查看:
完结。。。