多线程和高并发通关整理1-缓存一致性、java内存模型、synchronized、lock 和 volatile区别、锁、线程池原理、核心参数、线程创建

java通关整理汇总-Java基础、计算机网络、数据库、设计模式、框架、算法模板、笔试


一、多线程和高并发
1.缓存一致性问题

当程序在运行过程中,会将运算需要的数据从主存复制一份到 CPU 的高速缓存当中,那么 CPU 进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。

在单线程中,运行是没有问题的,但是在多线程中,由于每个线程可能是由不同的cpu执行(单核cpu也会出现缓存一致性的问题),当两个线程同时进行写操作时,就有可能在读写过程中出现一个修改后,另外一个线程只是在原来的基础上修改,导致结果和预期不大一样,出现缓存不一致的问题

2.怎么解决缓存一致性问题

1)通过在总线加 LOCK#锁的方式
2)通过缓存一致性协议

  • 早期的话,可以在总线上加锁,因为 CPU 和其他部件进行通信都是通过总线来进行的,如果对总线 加 LOCK#锁的话,也就是说阻塞了其他 CPU 对其他部件访问,从而使得只有一个CPU能使用这个变量。

第一种方式的问题:在锁住总线的期间,其他CPU无法访问内存,就会导致CPU无法访问内存,导致效率低下。

所以就出现了缓存一致性协议

  • 该协议保证了每个缓存中使用的共享变量副本是一致的。
  • 核心思想:当 CPU 向内存写入数据时, 如果发现操作 的变量是共享变量,即在其他 CPU 中也存在该变量的副本,会发出信号通知其 他 CPU 将该变量的缓存行置为无效状态,因此当其他 CPU 需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
3. 简述volatile关键字

一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰 之后,那么就具备了两层语义:

  • 1.保证了不同线程对这个变量进行读取时的可见性,即一个线程修改 了某个变量的值,这新值对其他线程来说是立即可见的。(volatile 解决了 线程间共享变量的可见性问题)。

这里说一下可见性的过程(加volatile关键字后线程读取过程)

  • 2.禁止进行指令重排序,阻止编译器对代码的优化。
4. 说一下java的内存模型

这里面试可以先说java模型规定,然后再说三个主要特性,把volatile留着,先把原子性、可见性、有序性还有happens-before说清楚,然后在解释volatile,对比synchronized、lock一起说

  • java内存模型规定所有的变量都是存在主内存当中,每个线程都有自己的工作内存。线程对变量的操作都是在工作内存上进行的,而不能直接对主内存进行操作,且不访问其他线程的工作内存。

三个主要特性:

  • Volatile 关键字
  • 要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。
    只要有一个没有被保证,就有可能会导致程序运行不正确。
  • happens-before 关系。(先行发生原则)

原子性: 即一个操作或者多个操作要么全部执行并且执行的过程不会被任
何因素打断,要么就都不执行。

可见性: 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线 程能够立即看得到修改的值。

  • 通过 Synchronized 和 Lock 和 volatile 实现“可见性”。

有序性: 即程序执行的顺序按照代码的先后顺序执行。

happens-before 原则(先行发生原则):

  1. 程序次序原则:写在前面的代码会先于后面的代码先执行
  2. volatile变量规则:对一个变量的写操作先于读操作
  3. 线程启动规则:线程的start()先于其他动作
  4. 线程中断规则、线程终结规则、对象终结规则
4. synchronized、lock 和 volatile
synochronizd和volatile关键字区别:
  • volatile关键字解决的是变量在多个线程之间的可见性;而sychronized关键字解决的是多个线程之间访问共享资源的同步性

  • volatile只能用于修饰变量,而synchronized可以修饰方法,以及代码块。(volatile是线程同步的轻量级实现,所以volatile性能比synchronized要好,并且随着JDK新版本的发布,sychronized关键字在执行上得到很大的提升,在开发中使用synchronized关键字的比率还是比较大)

  • 多线程访问volatile不会发生阻塞,而sychronized会出现阻塞。

  • volatile能保证变量在多个线程之间的可见性,但不能保证原子性;而sychronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。

  • 线程安全包含原子性和可见性两个方面。

  • 对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的。
    一句话说明volatile的作用:实现变量在多个线程之间的可见性。

synchronized和lock区别:

1)Lock是一个接口,而synchronized是Java中的关键字

2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)Lock可以提高多个线程进行读操作的效率(读写锁)。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

5. 锁类型(可重入锁、可中断锁、公平锁、非公平锁)

可重入锁:比如说,当一个线程执行method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
又比如,如果线程加载的两个方法都是加了synchronized的,在加载第二个的时候就不用加载了。

可中断锁:synchronized就不是可中断锁,而Lock是可中断锁。

公平锁:公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。

非公平锁:非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

6. Java 中的活锁,死锁,饥饿有什么区别?

死锁:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一 种互相等待的现象,若无外力作用,它们都将无法推进下去,此时称系统处于 死锁状态或系统产生了死锁。

饥饿锁:考虑一台打印机分配的例子,当有多个进程需要打印文件时,系统 按照短文件优先的策略排序,该策略具有平均等待时间短的优点,似乎非常合 理,但当短文件打印任务源源不断时,长文件的打印任务将被无限期地推迟, 导致饥饿以至饿死。

活锁:与饥饿相关的另外一个概念称为活锁 ,在忙式等待条件下发生的饥饿,称为活锁。
比如,事务1给一个数据加锁,事务2也来加锁,事务2就等待,这时候事务3也来加锁,当事务1释放锁后,事务3首先加锁,事务2还是等待,这时候事务4也来了,就这样事务2总是不断的尝试,称为活锁

避免活锁的简单方法是采用先来先服务的策略。

二、线程池问题
1. 什么是线程池?

线程池就是创建若干个可执行的线程放入一个池(容器)中, 有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。

2. 为什么使用线程池?

因为java创建一个线程,需要调用系统内核的API,需要操作系统为线程分配一系列的资源,成本很高,应该尽量避免创建、销毁,所以使用线程池

线程池是一种生产者-消费者模式

3. 线程池的工作原理
  • a.如果线程池中的数量小于corePoolSize,而线程池中的线程都处于空闲状态,也需要创建新的线程来处理任务。
  • b.如果线程池中的数量大于等于corePoolSize,但是缓冲队列未满,那么任务就会被放入缓冲队列中,等待空闲线程来去取出执行。
  • c.如果线程池中的数量大于等于corePoolSize,但是缓冲队列满时,并且线程池中的数量小于 maximumPoolSize,就会建立新的线程。
  • d.如果线程池中的数量大于等于corePoolSize,但是缓冲队列满时,并且线程池中的数量等于maximumPoolSize,就可以通过任务拒绝策略来处理次任务。
    处理任务的优先级为:当corePoolSize、缓冲队列、maximumPoolSize都满时,就会使用handler处理被拒绝的任务

在这里插入图片描述

4. 线程池的核心参数
  • corePoolSize  核心线程数,即就是中线程池中长时间稳定存活的线程数

  • maxPoolSize
      最大线程数,重点强调线程中最大可包含的线程数。最大线程数的上限需要根据实际情况而定

  • keepAliveTime
      线程的存活时间,该参数是指非核心线程的存活时间,用来严格控制线程池中线程的数量尽可能的保持在一定的范围内,若要修改核心线程的存活时长,可参考相关参数

  • ThreadFactory
      线程创建的工厂,新的线程都是由ThreadFactory创建的,系统默认使用的是Executors.defaultThreadFactory创建的,用它创建出来的线程的优先级、组等都是一样的,并且他都不是守护线程。我们也可以使用自定义的线程创建工厂,并对相关的值进行修改

  • WorkQueue
      线程的工作队列,常见的类型有三种,如下

1.直接交换:SynchronousQueue,任务不多,是没有容量的,maxPoolSize需要大一点

2.无界队列:LinkedBlockingQueue,可产生OOM

3.有界队列:ArrayBlockingQueue

三、线程
1. 创建线程的方法
  • 继承Thread方法
  • 实现Runnable接口
  • 实现Callable接口
  • 线程池
2. Runnable和Callable有什么不同?
  • Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
  • Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;
    Callable 接口 call 方法允许抛出异常,可以获取异常信息
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值