B 站学习视频遇见狂神说《JUC并发编程最新版通俗易懂》
java.util
工具包、包、分类线程、进程
进程:一个程序,QQ.exe Music.exe 程序的集合;一个进程往往可以包含多个线程,至少包含一个!
2
个线程,main和gc垃圾回收
线程:开了一个进程 Typora,写字,自动保存(线程负责的)
对于Java而言:Thread、Runnable、Callable
Java无法开启线程
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//这是一个C++底层,Java是没有权限操作底层硬件的
private native void start0();
并发、并行
并发(多线程操作同一个资源)
并行:多个人一起走
public class Test1{
public static void main(String[] args){
//获取CPU的核数
//CPU密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源
线程有几个状态
public enum State {
//运行
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
wait/sleep 区别
1、 来自不同的类
wait => Object
sleep => Thread
一般企业中使用的休眠是:
TimeUnit.DAYS.sleep(1);//休眠一天
TimeUnit.SECONDS.sleep(1);//休眠1s
2、 关于锁的释放
wait 会释放锁
sleep 睡觉了,不会释放!
3、 使用的范围是不同的
wait必须使用在代码块中
.
sleep 可以再任何地方睡
4、是否需要捕获异常
wait 不需要捕获异常
sleep 必须要捕获异常
传统 Synchronized
/**
* 真正的多线程开发
* 线程就是一个单独的资源类,没有任何的附属操作!
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
//多线程操作
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
//@FunctionalInterface 函数式接口 jdk1.8之后 lambda表达式
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();
}
}
//资源类
//属性+方法
//oop
class Ticket{
private int number=50;
//卖票的方式
// synchronized 本质:队列,锁
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
number--;
}
}
}
Lock 接口
公平锁:十分公平:可以先来后到
非公平锁:十分不公平:可以插队 (默认为非公平锁)
Synchronized 和 Lock 区别
锁是什么,如何判断锁的是谁!
面试的:单例模式、排序算法、生产者和消费者、死锁
生产者和消费者问题 Synchronized 版
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"B").start();
}
}
class Data{
//数字 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
if(number!=0){
//等待操作
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if(number==0){
//等待操作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我-1完毕了
this.notifyAll();
}
}
问题存在,A B C D 4 个线程! 虚假唤醒
解决方案:if 改为 while 判断
/**
* 判断等待,业务,通知
*/
class Data2{//资源类 数字
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//condition.await();//等待
//condition.signalAll();//唤醒全部
//+1
public void increment() throws InterruptedException {
try {
lock.lock();
//业务代码
while (number !=0){
//等待操作
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,我+1完毕了
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
try {
lock.lock();
//业务代码
while (number == 0){
//等待操作
condition.await();
}
number --;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,我-1完毕了
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
JUC版的生产者和消费者问题
通过Lock 找到 Condition
代码实现:
/**
* 判断等待,业务,通知
*/
class Data2{//资源类 数字
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//condition.await();//等待
//condition.signalAll();//唤醒全部
//+1
public void increment() throws InterruptedException {
try {
lock.lock();
//业务代码
while (number !=0){
//等待操作
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,我+1完毕了
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
try {
lock.lock();
//业务代码
while (number == 0){
//等待操作
condition.await();
}
number --;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,我-1完毕了
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
如何判断锁的是谁!
锁会锁住:对象、Class
深刻理解我们的锁:
问题一:标准情况下,两个线程先打印 发短信 还是 打电话
结果是:
那么问题来了,为什么是这种顺序来打印的,是按顺序执行的吗?答案显然是错误的!
问题二:我们让打短信延迟4s
现在结果是什么情况呢?
结果依然是:发短信 打电话!
原因: synchronized 锁的对象是方法的调用者!两个方法使用的是同一把锁,谁先拿到,谁先执行!
问题三:添加一个普通方法,结果先执行哪一个呢?
结果:先执行hello()
在打印发短信!原因是hello()
是一个普通方法,并不是同步方法,不受Synchronized
锁的影响,如果把发短信里的TimeUnit.SECONDS.sleep(4)
去掉,那么就会顺序执行,限制性发短信再执行hello()
。原因方法sendSms()
和hello()
两者并不会产生影响,在sendSms()
中加入延时时,在线程开始时,就会等待4s
再执行,去掉之后,两个方法并不会有谁等待谁的关系,就会按照顺序进行执行!(个人理解)
问题四:如果使用两个对象,分别调用发短信 和 打电话 那么顺序是什么呢?
结果: 打电话 发短信。
原因: 在发短信中 延时了4s
,再加上Synchronized
锁的对象是方法的调用者,如果有两把锁,就会根据执行时间来决定打印顺序!
问题 5、6 如果在方法上加上
static
变成静态方法!结果又该如何?
结果: 发短信 打电话
原因: 对于static
静态方法来说,对于整个类Class
来说只有一份,对于不同的对象使用的是同一份方法,相当于这个方法是属于这个类的,如果静态方法static
使用了Synchronized
锁定,那么这个Synchronized
锁会锁住整个对象!不管多少个对象,对于静态的锁都只有一把锁,谁先拿到这个锁谁先执行!其他进程都需要等待!
问题七:如果把两个方法设置为一个静态方法、一个同步方法,结果又将如何?
结果: 打电话 发短信
原因: 因为一个锁的是Class
类模板,一个锁的是对象的调用者,call()
不需要等待发短信,直接运行!
问题八:一个静态方法、一个同步方法,使用两个对象进行分别调用,顺序是什么呢?
结果: 打电话 发短信
原因: 因为两个对象,一样的原因,两把锁锁的不是同一个东西,所以后面的第二个对象不需要等待第一个对象去执行!
List不安全
//java.util.ConcurrentModificationException 并发修改异常
public class ListTest{
public static void main(String[] args){
List<String> list = new ArrayList<>();
for(int i = 1;i<=30;++i){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
},String.valueOf(i)).start();
}
}
}
结果:ArrayList
在并发情况下是不安全的!
解决方案:
Vector
就是线程安全的!public class ListTest{
public static void main(String[] args){
List<String> list = new Vector<>();
for(int i = 1;i<=30;++i){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
},String.valueOf(i)).start();
}
}
}
Collections.synchronizedList(new ArrayList<>());
public class ListTest{
public static void main(String[] args){
List<String> list = Collections.synchronizedList(new ArrayList<>());
for(int i = 1;i<=30;++i){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
},String.valueOf(i)).start();
}
}
}
JUC
中的包:List list = new CopyOnWriteArrayList<>();
public class ListTest{
public static void main(String[] args){
List<String> list = new CopyOnWriteArrayList<>();
for(int i = 1;i<=30;++i){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
},String.valueOf(i)).start();
}
}
}
CopyOnWriteArrayList
:写入时复制!COW计算机程序设计领域的一种优化策略
多个线程调用的时候,list,读取的时候,固定的,写入(覆盖);在写入的时候避免覆盖,造成数据的问题!
CopyOnWriteArrayList 比 Vector 厉害在哪里?
Vector
的addElement()
方法使用的是Synchronized
,一般使用Synchronized
效率较低Set不安全
和List
属于同级,由于List
在并发情况下不安全,则Set
也是不安全的
解决方案:
Collections
工具类的synchronized
包装的Set
类CopyOnWriteArraySet
写入复制的JUC
解决方案//java.util.ConcurrentModificationException
public class SetTest {
public static void main(String[] args) {
Set<String> set1 = new HashSet<>();
//Set set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 100; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
HashSet底层是什么?
通过底层来看,HashSet
的底层其实就是一个HashMap
public HashSet() {
map = new HashMap<>();
}
//add 本质其实就是一个map的key,map的key是无法重复的,所以使用的就是map存储
//hashSet就是使用了hashmap key不能重复的原理
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//PRESENT是什么? 是一个常量 不会改变的常量 无用的占位
private static final Object PRESENT = new Object();
Map不安全
//map是这样用的吗? 不是,工作中不使用HashMap
//默认等价于什么? new HashMap<>(16,0.75);
//Map<String, String> map = new HashMap<>();
HashMap
默认加载因子是0.75
,默认的初始容量是16
同样的HashMap
基础类也存在并发修改异常!
public class HashMapTest {
public static void main(String[] args) {
//map是这样用的吗? 不是,工作中不使用HashMap
//默认等价于什么? new HashMap<>(16,0.75);
//Map map = new HashMap<>();
// Collections.synchronizedMap()
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
解决方案:
Collections.synchronizedMap(new HashMap<>());
处理new ConcurrentHashMap<>()
进行并发处理run()/call()
代码测试
public class CallableTest{
public static void main(String[] args){
for(int i = 1;i<10;++i){
new Thread(new MyThread()).start;
}
}
}
class MyThread implements Runnable{
@Override
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
使用Callable进行多线程操作:
Callable如何放入到Thread里面呢?
对于Thread
运行,只能传入Runnable
类型的参数
FutureTask
中可以接受Callable
参数
这样我们就可以先把Callable
放入FutureTask
中,如何再把FutureTask
放入到Thread
就可以了!
public class CallableTest {
public static void main(String[] args)throws Exception {
//new Thread(new Runnable()).start();
//new Thread(new FutuerTask()).start
//构造器
//new Thread(new FutuerTask( Callable )).start
for (int i = 1; i <= 10; i++) {
MyThread thread = new MyThread();
//适配类:FutureTask
FutureTask<String> futureTask = new FutureTask<>(thread);
//放入Thread使用
new Thread(futureTask,String.valueOf(i)).start();
//获取返回值
String s = futureTask.get();
System.out.println("返回值"+s);
}
}
}
class MyThread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("Call:"+Thread.currentThread().getName());
return "String"+Thread.currentThread().getName();
}
}
这样我们就可以使用Callable
来进行多线程编程了。并且我们发现可以有返回值 了,并且可以抛出异常
其实就是一个减法计数器,对于计数器归零之后在进行后面的操作,这是一个计数器
//计数器
public class CountDownLatchDemo {
public static void main(String[] args) {
//总数是6 必须要执行的任何的时候,再使用!
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"走了");
countDownLatch.countDown();// 数量-1
},String.valueOf(i)).start();
}
try {
countDownLatch.await();//等待计数器归 0 ,然后再向下执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("关门");
}
}
主要方法:
countDown
减一操作await
等待计数器归零await
等待计数器为0,就唤醒,再继续向下执行。
其实就是一个加法计数器
public class CyclicBarrierDemo {
public static void main(String[] args) {
/*
*集齐七颗龙珠召唤神龙
* */
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功!");
});
for (int i = 1; i <= 7 ; i++) {
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集了"+temp+"颗龙珠");
try {
cyclicBarrier.await();//等待
} catch(Exception e){
e.printStackTrace();
}
}).start();
}
}
}
Semaphore
:信号量
代码模拟抢车位
public class SemaphoreDemo {
public static void main(String[] args) {
/**
* 参数:线程数量 应用场景:限流!
*/
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 3; 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();
}
}
}
原理:
semaphore.acquire()
获取资源,如果资源已经使用完了,就等待资源释放后再进行使用!
semaphore.release()
释放,会将当前的信号量释放+1,然后唤醒等待的线程!
应用场景:
BlockingQueue
什么情况下会使用阻塞队列?
线程A调用线程B,则A必须等待线程B执行完之后才能执行
学会使用队列
方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞 等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(,) |
移除 | remove() | poll() | take() | poll(,) |
检测队首元素 | element() | peek() | - | - |
/**
* 抛出异常
*/
public static void test1(){
//参数:队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
// java.lang.IllegalStateException: Queue full 队列满
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
System.out.println("========================");
// java.util.NoSuchElementException
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
}
/**
* 不抛出异常
*/
public static 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"));//false 不抛出异常!
System.out.println("-=======================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());//null 不抛出异常!
}
/*
* 等待,阻塞(一直阻塞)
* */
public static void test3() throws InterruptedException {
ArrayBlockingQueue<Object> 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 test4() throws InterruptedException {
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", 3, TimeUnit.SECONDS));//超时2秒
System.out.println("==========================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}
SynchronousQueue 同步队列
/**
* 同步队列
* 和其他的BlockingQueue 不一样,SynchronousQueue 不存储元素
* put了一个元素,必须从里面先take取出来,否则不能在put进去值!
*/
public class SynchronousQueueTest {
public static void main(String[] args) {
SynchronousQueue<String> 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 (Exception e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+ synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
} catch (Exception e) {
e.printStackTrace();
}
},"T2").start();
}
}
池化技术
程序的运行,本质:占用系统的资源!优化资源的使用!=> 池化技术
线程池、连接池、对象池、内存池///… 创建、销毁十分浪费资源
池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我。
线程池的好处:
线程复用,可以控制最大并发数,管理线程
线程池:三大方法
/**
* @function Executors 工具类、3大方法
* @author 派 大 星
* @date 2022/3/31 22:49
*/
public class Demo01 {
public static void main(String[] args) {
// 单个线程
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 创建一个固定的线程池的大小
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 可伸缩的,遇强则强,遇弱则弱
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 1; i <= 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
// 线程池用完,程序执行完关闭线程池
threadPool.shutdown();
}
}
}
7大参数
源码分析:
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 ,// 约21亿 oom溢出
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
手动创建一个线程池
/**
* @author 派 大 星
* @function
* @date 2022/4/1 7:12
* 四种拒绝策略
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
for (int i = 1; i <= 9; i++) {
//使用自定义线程池创建线程
//最大承载:Deque + max(双端队列 + 最大线程池数)
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程
threadPoolExecutor.shutdown();
}
}
}
四种拒绝策略
/**
* new ThreadPoolExecutor.AbortPolicy() 银行满了,还有人进来,不处理这个人,抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() 哪来的去哪里!
* new ThreadPoolExecutor.DiscardPolicy() 队列满了,丢掉任务,不会抛出异常!
* new ThreadPoolExecutor.DiscardOldestPolicy() 队列满了。尝试去和最早的竞争(竞争成功,执行,竞争不成功,则丢掉任务),也不会抛出异常!
*/
小结和拓展
了解:IO密集型、CPU密集型
// 最大线程到底如何定义?
// 1. CPU 密集型 CPU几核,就是几,可以保持CPU的效率最高!Runtime.getRuntime().availableProcessors()
// 2. IO 密集型 > 判断你程序中十分消耗资IO的线程
// 程序 15个大型任务,io十分占用资源
新时代程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
//简化编程模型,在新版本框架底层大量应用
//foreach(消费者类型的函数式接口)
Function
函数式接口
/**
* @author 派 大 星
* @function
* @date 2022/4/1 8:05
* Function 函数型接口,有一个输入参数,有一个输出
* 只要是函数型接口,可以用lambda表达式简化
*
*/
public class Demo01 {
public static void main(String[] args) {
// 工具类:输出输入的值
//
// Function function = new Function() {
// @Override
// public String apply(String s) {
// return s;
// }
// };
// lambda表达式简化
Function<String, String> function = (str)->{return str;};
System.out.println(function.apply("123"));
}
}
Predicate
断定型接口:有一个输入参数,返回值只能是 布尔值!
/**
* @author 派 大 星
* @function
* @date 2022/4/1 19:42
* 断定性接口:有一个输入参数,返回值只能是 布尔值!
*/
public class Demo02 {
public static void main(String[] args) {
//判断字符串是否为空
// Predicate predicate = new Predicate() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// };
Predicate<String> predicate = (str)->{
return str.isEmpty();
};
System.out.println(predicate.test(""));
}
}
Consumer
消费型接口
/**
* @author 派 大 星
* @function
* @date 2022/4/1 19:48
* Consumer 消费型接口:只有输入,没有返回值!
*/
public class Demo03 {
public static void main(String[] args) {
// Consumer consumer = new Consumer() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// };
Consumer<String> consumer =(str)->{
System.out.println(str);
};
consumer.accept("dadada");
}
}
Supplier
供给型接口
/**
* @author 派 大 星
* @function
* @date 2022/4/1 19:52
* Supplier 消费型接口:没有参数,只有返回值!
*/
public class Demo04 {
public static void main(String[] args) {
// Supplier supplier = new Supplier() {
// @Override
// public String get() {
// return "null";
// }
// };
Supplier<String> supplier =()->{return "xxxx";};
System.out.println(supplier.get());
}
}
什么是Stream流式计算?
大数据:存储+计算
集合、Mysql 本质就是存储东西!
/**
* @author 派 大 星
* @function
* @date 2022/4/1 20:14
* 题目要求:一分钟内完成此题,只能用一行代码实现!
* 现有5个用户!筛选
* 1. ID必须是偶数
* 2. 年龄必须大于23
* 3. 用户名转为大写字母
* 4. 用户名字母倒着排序
* 5. 只输出一个用户
*/
public class Test {
public static void main(String[] args) {
User user1 = new User(1,"a",21);
User user2 = new User(2,"b",22);
User user3 = new User(3,"c",23);
User user4 = new User(4,"d",24);
User user5 = new User(6,"e",25);
List<User> users = Arrays.asList(user1, user2, user3, user4, user5);
//集合就是存储
users.stream()
// 过滤 filter(Predicate super T> predicate);
.filter(u->{return u.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
// map(Function super T, ? extends R> mapper);
.map(u->{return u.getName().toUpperCase();})
//sorted(Comparator super T> comparator);
.sorted((u1,u2)->{return u2.compareTo(u1);})
.limit(1)
.forEach(System.out::println);
}
}
什么是ForkJoin
ForkJoin在JDK1.7 ,并行执行任务!提高效率,大数据量!
ForkJoin特点:工作窃取
这个里面维护的都是双端队列
如何理解工作窃取
:现有两个线程A、B,假设B提前执行结束,但是B不能一直等待A执行结束,所以B会窃取
A的任务进行执行
ForkJoin如何使用
/**
* @author 派 大 星
* @function
* @date 2022/4/1 21:04
* 求和计算任务
* 如何使用ForkJoin
* 1. ForkJoinPool 通过它来执行
* 2. 计算任务 ForkJoinPool.execute(ForkJoinTask task)
* 3. 计算类必须继承 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;
}
public void test(){
}
//计算方法
@Override
protected Long compute() {
if (end-start < temp){
Long sum = 0L;
for (Long i = start; i <= end; i++) {
sum+=i;
}
return sum;
}else {//使用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();
}
}
}
测试:
/**
* @author 派 大 星
* @function
* @date 2022/4/1 21:35
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test3();
}
public static void test1(){
//3724
Long sum = 0L;
long start = System.currentTimeMillis();
for (int i = 1; i <= 10_0000_0000; i++) {
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
/**
* 使用ForkJoin调优的
* 3922
*/
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
//提交任务
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
/**
* 185
*/
public static void test3(){
long start = System.currentTimeMillis();
//Stream并行流
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}
}
Future设计的初衷:对将来的某个事件的结果进行建模
/**
* @author 派 大 星
* @function
* @date 2022/4/1 22:42
*
* 异步调用:CompletableFuture
* 异步执行
* 成功回调
* 失败回调
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值的 runAsync 异步回调
// CompletableFuture completableFuture = CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"runAsync => Void");
// });
// System.out.println("111111");
// //阻塞获取执行结果
// completableFuture.get();
// 有返回值的 supplyAsync 异步回调
//ajax 成功和失败的回调
// 返回的是错误信息
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync => Integer");
int i = 10/0;
return 1024;
});
Integer integer = completableFuture.whenComplete((t, u) -> {
//t 为正常的返回结果
System.out.println("t=>" + t);
//u 为失败的返回结果
System.out.println("u=>" + u);
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 233;
}).get();
System.out.println(integer);
}
}
请你谈谈你对Volatile的理解
Volatile是Java虚拟机提供 的轻量级的同步机制
不保证原子性
什么是JMM
JMM:Java内存模型,不存在的东西!
关于JMM的一些同步的约定:
立刻
刷回主存线程:工作内存、主内存
内存交互操作有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操作之前,必须把此变量同步回主内存
public class JmmDemo {
private static int num = 0;
public static void main(String[] args) {//main线程
// 线程1
new Thread(()->{
while (num == 0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
问题:程序不知道主内存的值已经被修改过了
1、保证可见性
public class JmmDemo {
/**
* 不加volatile 程序就会死循环
* 加volatile 可以保证可见性
*/
private volatile static int num = 0;
public static void main(String[] args) {//main线程
// 线程1 对主内存的变化是不知道的
new Thread(()->{
while (num == 0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
2、不保证原子性
原子性:不可分割
public class VDemo02 {
/**
* volatile 不保证原子性
*/
private volatile static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) {
//理论上结果为20000
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
如果不加lock 和 Synchronized ,怎么样保证原子性
原子类为什么这么高级
public class VDemo02 {
/**
* volatile 不保证原子性
*/
private static AtomicInteger num = new AtomicInteger();
public static void add(){
// AtomicInteger的+1操作
num.getAndIncrement();
}
public static void main(String[] args) {
//理论上结果为20000
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
指令重排
什么是指令重排:你写的程序,计算机并不是按照你写的那样去执行的。
源代码 -> 编译器优化的重排 -> 指令并行也可能重排 -> 内存系统也会重排 --> 执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性!
int x = 1; //1
int y = 4; //2
x = x + 4; //3
y = x * x; //4
我们所期望的:1234 但是可能执行的时候会变成 2134 1324
可不可能是 4123!
可能造成影响的结果:a b x y 这四个值默认都是 0
线程A | 线程B |
---|---|
x = a | y = b |
b = 1 | a = 2 |
正常的结果 :x = 0; y = 0;但是可能由于指令重排
线程A | 线程B |
---|---|
x = a | y = b |
b = 1 | a = 2 |
指令重排导致的诡异结果:x = 2;y = 1;
Volatile可以避免指令重排:
内存屏障。CPU指令,作用:
Volatile是可以保持 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
内存屏障在单例模式中使用的最多!
饿汉式
/**
* @author 派 大 星
* @function 饿汉式单例
* @date 2022/4/2 21:09
*/
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懒汉式
/**
* @author 派 大 星
* @function 懒汉式单例模式
* @date 2022/4/2 21:48
*/
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class){
if (lazyMan != null){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
System.out.println(Thread.currentThread().getName()+" ok");
}
private static LazyMan lazyMan;
/**
* 双重检测锁模式的 懒汉式单例 DCL懒汉式
*/
public static LazyMan getInstance(){
if (lazyMan == null){
synchronized (LazyMan.class){
if (lazyMan == null){
lazyMan = new LazyMan();
//不是一个原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
// 破除私有权限
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
/**
* 1. 分配内存空间
* 2. 执行构造方法,初始化对象
* 3. 把这个对象指向这个空间
*
* 123
* 132 A
* B 此时lazyMan还没有完成构造
*/
静态内部类
/**
* @author 派 大 星
* @function 静态内部类
* @date 2022/4/2 22:02
*/
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private final static Holder HOLDER = new Holder();
}
}
单例不安全,反射
枚举
/**
* @author 派 大 星
* @function enum是一个什么?本身也是一个Class类
* @date 2022/4/2 22:11
*/
public enum EnumSingle {
INSTANCE;
public static EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
// 破除私有权限
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance();
System.out.println(enumSingle);
System.out.println(instance);
}
}
什么是CAS
public class CASDemo {
//CAS
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2022);
// 期望值 ,更新值
// public final boolean compareAndSet(int expect, int update)
// 如果我期望的值达到了,那么就更新,否则,就不更新 ,CAS 是CPU的并发原语
System.out.println(atomicInteger.compareAndSet(2022, 2023));
System.out.println(atomicInteger.get());
atomicInteger.getAndIncrement();
System.out.println(atomicInteger.compareAndSet(2022, 2023));
}
}
Unsafe类
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
缺点:
CAS:ABA问题(狸猫换太子)
解释:现有A、B线程对资源A进行操作(操作同一资源),线程A中含有CAS操作,
当A = 1时,将1 修改为2 。线程B中同样含有CAS操作,当A = 1时,
将A替换成3,但是B的线程执行速度很快,在进行CAS操作时,将A修改为3后,又将3修改为了1,
此时线程A再进行CAS操作时,A的值虽然为1,但是A的值已经被修改过了!
public class CASDemo {
//CAS
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2022);
// 期望值 ,更新值
// public final boolean compareAndSet(int expect, int update)
// 如果我期望的值达到了,那么就更新,否则,就不更新 ,CAS 是CPU的并发原语
//=================捣乱的线程=====================
System.out.println(atomicInteger.compareAndSet(2022, 2023));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2023, 2022));
System.out.println(atomicInteger.get());
//=================期望的线程=====================
System.out.println(atomicInteger.compareAndSet(2022, 6666));
System.out.println(atomicInteger.get());
}
}
解决ABA问题,引用原子引用!对应的思想(乐观锁)
带版本号的原子操作!
public class atomicReferenceDemo {
public static void main(String[] args) {
// AtomicStampedReference 注意:如果泛型是一个包装类,注意对象的引用问题
// 正常的业务操作,这里引用的都是一个个对象
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(1,1);
// CAS compareAndSet: 比较并交换!
// 乐观锁的原理一样!
new Thread(()->{
//获取版本号
int stamp = atomicStampedReference.getStamp();
System.out.println("a1 =>"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a2 =>"+stamp);
System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3 =>"+stamp);
},"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, 6, stamp, stamp + 1));
System.out.println("b2 =>"+atomicStampedReference.getStamp());
},"b").start();
}
}
Integer使用了对象缓存机制,默认范围是 -128~127,推荐使用静态工厂方法valueOf获取对象的实例,而不是new ,因为valueOf使用缓存,而new 一定会创建新的对象分配新的内存空间!
公平锁:非常公平,比如一个队列不能插队的,必须先来后到
非公平锁:非常不公平,可以插队(默认都是非公平锁)比如l两个任务耗时分别是3s、3h,不可能让3s一直等待3h的完成后再执行
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.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"sms");
//这里也有一把锁
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
}
}
Lock锁
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone2{
Lock lock = new ReentrantLock();
public void sms(){
// 细节问题:lock.lock(); lock.lock(); Lock 锁必须配对,否则就会死在里面
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"sms");
//这里也有一把锁
call();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"call");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
自定义自旋锁
public class SpinLockDemo {
/**
* int 0
* Thread null
*/
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
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 static void main(String[] args) {
// ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e){
e.printStackTrace();
}finally {
spinLockDemo.myUnLock();
}
},"T1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}finally {
spinLockDemo.myUnLock();
}
},"T2").start();
}
}
死锁是什么?
死锁测试:怎么排除死锁
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;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"lock:"+lockA+" => "+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+" lock:"+lockB+" => "+lockA);
}
}
}
}
解决问题
jps -l
定位进程号jstack 进程号
找到死锁问题面试或工作中排查问题