JUC学习-狂神

线程与进程

1.一个进程之内可以分为一到多个线程。
2.一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
3.Java 中,线程作为最小调度单位,进程作为资源分配的最小单位

1.进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
2.进程拥有共享的资源,如内存空间等,供其内部的线程共享
3.进程间通信较为复杂
a.同一台计算机的进程通信称为 IPC(Inter-process communication)
b.不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
4.线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
5.线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

并发与并行

并发(一人多事)—家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事,这时就是并发
既有并发又有并行—家庭主妇雇了个保姆,她们一起这些事,这时既有并发,也有并行(这时会产生竞争,例如锅只有一口,一个人用锅时,另一个人就得等待)
并行(各做个事不冲突)—雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰,这时是并行

面试:单例,排序算法,生产者消费者,死锁。

4、生产者和消费者问题(线程通信)

创建多个线程去执行不同的任务,如果这些任务之间有着某种关系,那么线程之间必须能够通信来协调完成工作。
生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据
注意:生产者-消费者模式中的内存缓存区的主要功能是数据在多线程间的共享,此外,通过该缓冲区,可以缓解生产者和消费者的性能差;

Synchronized版本 :问题存在,A线程B线程,没有问题,但现在如果我有四个线程A B C D!,就有问题,存在虚假唤醒。因为if只会判断一次
用if判断多线程会存在虚假唤醒的问题,改用while可避免该问题。if 改为while即可,防止虚假唤醒,保证线程是安全的。

Condition的优势:精准的通知和唤醒的线程

先调用不一定先执行。

静态方法是类一加载就有了。
异常:oom,栈溢出,内存溢出。

5、8锁问题

8锁问题
如果两个锁上锁的是同一个对象,则必须要等到一个方法执行结束才能执行下一个方法,而当两个锁上锁的不是同一个对象,则当一个方法发生执行延迟时,就可以先执行下一个方法!

我们可以采用synchronized 给方法上锁,即表示方法是普通同步锁,使用synchronized 上锁的是方法的调用者;

我们也可以采用static synchronized 给方法上锁,表示方法是静态的同步方法锁,使用static synchronized 上锁锁的是资源类的Class类模板,而Class类模板是全局唯一;

规律:如果上锁的是同一个对象,则执行会按照先后顺序依次执行,只有前面的方法执行结束,才能执行后面的方法;如果上锁的不是同一个对象,首先也会按照先后顺序执行,一旦执行的过程中发现有sleep方法,即延迟,有CPU的空闲,就会执行下面与它不是同一个锁的方法,提高CPU执行的效率!

原文链接:8锁原文参考

6、集合不安全

并发情况下集合可能不安全,JUC中提供了并发下的各种集合类

ArrayList 在并发情况下是不安全的!会出现: java.util.ConcurrentModificationException

vactor比ArrayList先出来且是线程安全的,vactor源码中加了 Synchronized 效率特别低下。。

解决:使用JUC中的包:List arrayList = new CopyOnWriteArrayList<>();

CopyOnWriteArrayList用到了写时复制的技术,读写分离的思想来实现的,CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!

CopyOnWrite容器即写时复制的容器。往一个容器中添加一个元素的时候,不直接往当前容器Object[]中添加,而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后新的容器里Object[] newElements添加元素,添加元素之后,再将原容器的引用指向新的容器setArray(newElements)。这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前元素不会添加任何元素。所有CopyOnWrite容器也是一种读写分离的思想,读和写是不同的容器。

Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;
解决方案还是两种:
• 使用Collections工具类的synchronized包装的Set类
• 使用CopyOnWriteArraySet 写入复制的JUC解决方案

HashSet底层是什么?

hashSet底层就是一个HashMap;
//add 本质其实就是一个map的key,map的key是无法重复的,所以使用的就是map存储
//hashSet就是使用了hashmap key不能重复的原理

同样的HashMap基础类也存在并发修改异常!
解决方案:
• 使用Collections.synchronizedMap(new HashMap<>());处理;
• 使用ConcurrentHashMap进行并发处理

futureTask.get(); 这个方法可能会产生阻塞,因为要等待结果的返回,等待结果的过程可能会很耗时,所以一般把这个放到最后。

起了两个线程,但call 只执行了一次,因为结果被缓存,效率更高。

8、常用的辅助类(必会!)

8.1 CountDownLatch

其实就是一个减法计数器,对于计数器归零之后再进行后面的操作,这是一个计数器!
主要方法:
• countDown 减一操作;
• await 等待计数器归零。
await等待计数器为0,就唤醒,再继续向下运行。

8.2 CyclickBarrier

其实就是一个加法计数器;
lambda表达式是new了一个类,正常是拿不到for循环里的变量,要拿,可以通过final

8.3 Semaphore

Semaphore:信号量
原理:
semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!
semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!

9、读写锁

如果我们不加锁的情况,多线程的读写会造成数据不可靠的问题。
我们也可以采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。
但是这次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证

ReadWriteLock也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个资源可以被多个线程同时读,或者被一个线程写,但是不能同时存在读和写线程。
读写互斥,读读共享。
读的时候可以多个线程一起读
写的时候只能一个线程写

10、阻塞队列

blockingQueue 是Collection的一个子类;
什么情况我们会使用 阻塞队列呢?
多线程并发处理、线程池!

SynchronousQueue同步队列
同步队列 没有容量,也可以视为容量为1的队列;

进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;

put方法 和 take方法;

Synchronized 和 其他的BlockingQueue 不一样 它不存储元素;

put了一个元素,就必须从里面先take出来,否则不能再put进去值!

并且SynchronousQueue 的take是使用了lock锁保证线程安全的。

11、线程池(重点)

线程池:三大方法、7大参数、4种拒绝策略
线程池的好处:
1、降低资源的消耗;
2、提高响应的速度;
3、方便管理;
线程复用、可以控制最大并发数、管理线程;

线程池:三大方法

ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的

7大参数

本质:三种方法都是开启的ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,  //核心线程池大小
                          int maximumPoolSize, //最大的线程池大小
                          long keepAliveTime,  //超时了没有人调用就会释放
                          TimeUnit unit, //超时单位
                          BlockingQueue<Runnable> workQueue, //阻塞队列
                          ThreadFactory threadFactory, //线程工厂 创建线程的 一般不用动
                          RejectedExecutionHandler handler //拒绝策略
                         ) {

拒绝策略4种

(1)new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常
超出最大承载,就会抛出异常:队列容量大小+maxPoolSize
(2)new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理
(3)new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。
(4)new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常

如何去设置线程池的最大大小如何去设置?

CPU密集型和IO密集型!
1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小
2、I/O密集型:
在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。

12、四大函数式接口(必需掌握)

新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算

13、Stream流式计算

什么是Stream流式计算?
存储+计算!

.parallel().reduce(0, Long::sum)使用一个并行流去计算整个计算,提高效率。

15、异步回调

Future 设计的初衷:对将来的某个事件结果进行建模!
其实就是前端 --> 发送ajax异步请求给后端
(1)没有返回值的runAsync异步回调 CompletableFuture.runAsync
(2)有返回值的异步回调supplyAsync CompletableFuture.supplyAsync

16.JMM

多线程操作
number++;
是不安全的,如果不加lock和synchronized ,怎么样保证原子性?解决方法:使用JUC下的原子包下的class;
private static volatile AtomicInteger number = new AtomicInteger();
number.incrementAndGet(); //底层是CAS保证的原子性

• volatile可以保证可见性;
• 不能保证原子性
• 由于内存屏障,可以保证避免指令重排的现象产生
面试官:那么你知道在哪里用这个内存屏障用得最多呢?单例模式

18、玩转单例模式

饿汉式、DCL懒汉式
饿汉式:类加载的时候就实例化,并且创建单例对象。

单例模式有以下特点:

  1. 单例类只能有一个实例;

  2. 单例类必须自己创建自己的唯一实例;

  3. 单例类必须给所有其他对象提供这一实例;

  4. 优缺点
    优点:由于单例模式只生成了一个实例,所以能够节约系统资源,减少性能开销,提高系统运行效率。
    缺点:因为系统中只有一个实例,导致了单例类的职责过重,违背了“单一职责原则”,同时不利于扩展。

我给面试官讲解了单例模式后,他对我竖起了大拇指
原文链接:单例模式讲解参考

19、深入理解CAS

什么是CAS?

//CAS : compareAndSet 比较并交换
原子类中的操作
 //boolean compareAndSet(int expect, int update)         
 //期望值、更新值         
 //如果实际值 和 我的期望值相同,那么就更新        
  //如果实际值 和 我的期望值不同,那么就不更新

总结:
CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
缺点:
• 循环会耗时;
• 一次性只能保证一个共享变量的原子性;
• 它会存在ABA问题

CAS:ABA问题?(狸猫换太子)

20、原子引用

解决ABA问题,对应的思想:就是使用了乐观锁~

带版本号的 原子操作!
int stamp = atomicStampedReference.getStamp(); // 获得版本号 每次操作后修改版本号,多线程时,就会感知到不同版本号,也就知道是否对所修改的值是没有被动过,解决了ABA问题。

Integer
使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。

transient临时的
volatile不稳定的
自旋锁,不断的去尝试直到成功为止

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值