JAVA并发

1、final关键字的作用都有那些?

  1. 修饰变量
    当一个变量被声明为 final 时,它的值一旦赋值后就不能再更改。这适用于基本数据类型和引用类型。修饰‌引用类型时,表示该引用无法再指向其他对象,但引用所指向的对象的内容可以发生改变。
  2. 修饰方法 被 final 修饰的方法不能被子类重写。这有助于防止改变方法的行为。
  3. 修饰类 当一个类被声明为 final 时,不能被继承。这可以防止类的扩展,确保其行为不被改变。
  4. 修饰参数 在方法参数中使用 final 可以防止在方法内部修改该参数的值,增加代码的可读性和安全性。
  5. 内存可见性 在多线程环境中,使用 final 变量可以确保在一个线程中初始化的对象,对其他线程是可见的。这是因为在 Java 内部,final 变量的写操作和对该变量的读取会添加内存屏障,从而确保:其他线程看到 final 变量的值时,一定是对象的完全初始化状态。
  6. 构造函数和 final 变量 如果一个对象的构造函数中初始化了 final 变量,那么一旦构造函数完成,这个对象的 final 变量的值对所有线程都是可见的。这是因为 JVM 会在构造器完成后插入一个内存屏障,确保所有写入的字段都对其他线程可见。

2、volatile关键字的作用?

volatile关键字主要有三方面作用:
1.实现long/double类型变量的原子操作(写/读,写/写)
2.防止指令重复排序
a.当用volatile修饰的时候,变量不会放到寄存器中存取该变量的值,而是从内存中获取。
3.实现变量的可见性

  • volatile与锁类似的地方有两点:
    a.确保变量的内存可见性。
    b.防止指令重排序
    注意
    volatile可以确保对写变量操作的原子性,但不具备排他性。另外一点在于:锁可以导致线程上下文之间的切换(内核态和用户态之间的切换),但使用volatile不会出现这种情况
  • 如果要实现volatile写操作的原子性,那么在等号右侧的赋值变量中就不能出现被多线程所共享的变量,哪怕这个变量被volatile关键字修饰也不可以。

3、讲讲volatile与内存屏障?

防止指令重复排序与实现变量的可见性都是通过一种手段来实现,内存屏障(memory barrier)
int a = 1;
String s = “hello”;
内存屏障(Release Barrier,释放屏障)
volatile boolean v = false; //写入操作
内存屏障(Store Barrier,存储屏障)
1.Release Barrier防止下面的volatile与上面的任何指令重排序
2.Release Barrier之前的所有操作,不管是写,还是读都可以发布到其他线程之中,让其他线程可以看到修改的结果。
3.Store Barrier重要的作用是刷新处理器的缓存,结果就是可以确保该存储屏障之前的一切操作所生成的结果对于其他处理器来说都可见。
内存屏障(Load Barrier, 加载屏障)
volatile boolean v1 = v;
内存屏障(Acquire Barrier, 获取屏障)
int a = 1;
String s = “hello”;
1.Load Barrier, 加载屏障,可以刷新处理器缓存,同步其他处理器对该volatile变量的修改结果
2.Acquire Barrier, 获取屏障可以防止上面的volatile读取操作与下面的所有操作语句进行重排序。

总结
对于volatile关键字的变量的读或者写,都是通过内存屏障来执行的,内存屏障兼备了两方面能力:1.防止指令重排序,2.实现变量内存可见性。
1.对于读取操作来说,volatile可以确保该操作与其后续的所有读写操作不会进行指令重排序。
2.对于修改操作来说,volatile可以确保该操作与其上面的所有读写操作不会进行指令重排序。

4、Java中的线程安全集合都有那些?

Java中线程安全的集合包括:
1.Vector:同步的动态数组。
2.Hashtable:同步的键值对映射。
3.Collections.synchronizedList:将普通列表包装为线程安全。
4.Collections.synchronizedMap:将普通映射包装为线程安全。
5.ConcurrentHashMap:高效的并发哈希映射。
6.CopyOnWriteArrayList:写时复制的线程安全列表。
7.CopyOnWriteArraySet:写时复制的线程安全集合

5、Java中的线程有那些状态?

1、NEW
尚未启动的线程处于此状态。
2、RUNNABLE
在Java虚拟机中执行的线程处于这种状态。
3、BLOCKED
被阻塞等待monitor lock的线程处于此状态。
4、WAITING
无限期地等待另一个线程执行特定操作的线程处于此状态。
5、TIMED_WAITING
等待另一个线程执行操作长达指定等待时间的线程处于此状态。
6、TERMINATED
已退出的线程处于此状态。

6、Java中的多线程之间如何通讯?

传统上,我们可以通过synchronized关键字 + wait + notify/notifyAll 来实现多个线程之间的协调和通信,整个过程都是由jvm来帮助我们实现的开发者是无需/无法了解底层的实现细节

从jdk5开始,并发包提供了Lock,Condition,await,signal,signalAll来实现多个线程之间的协调与通信,这个过程都是由开发者来控制的,相比于传统模式,更加灵活,更加强大。

Condition 一个对象可以有多个WaitSet

Thread.sleep与await的本质区别:sleep方法本质上不会释放锁,而await会释放锁,并且在signal之后,并且需要重新获取锁才能执行。

7、谈谈Java中的Synchronized关键字的理解?

1、synchronized关键字用来实现代码块同步。可以使用在实例方法,静态方法,代码块上。
2、用在实例方法和代码块上锁的是该对象,并且在JVM字节码上有ACC_SYNCHRONIZED标志。用来静态方法上锁的时类的class对象。在字节码上有ACC_SYNCHRONIZED和ACC_STATIC标志。
3、用在代码块上在字节码上是monitorentry和monitorexit标记。

8、为什么要锁升级?

synchronized关键字在java底层是通过互斥锁实现的,互斥锁是操作系统层面基于mutex lock实现的,这牵扯到了用户态和内核态的切换,在锁竞争非常激烈和并发特别高的时候这种切换非常影响锁的性能。所以Java中的对象就将锁进行了优化,从无锁状态到偏向锁,再到轻量级锁,再到重量级锁从而减少了锁带来的用户态和内核态的切换。这些锁的优化实际都是在Java的对象头中的一些标志位MarkWord来实现的。

9、锁升级的过程是什么?

1.对于synchronized锁升级的过程,锁的升级主要是通过Mark Word中的锁标志位与是否偏向锁来达成的。synchronized关键字对应的锁都是从偏向锁,随这锁的竞争不断升级,逐步演化至轻量级锁,最后则变成了重量级锁
2.偏向锁主要针对一个线程来说,他的主要作用就是优化同一个线程多次获取一个锁的情况,如果一个synchronized方法被一个线程访问,那么这个synchronized的对象就会在其Mark Word中的偏向锁进行标记,同时还会来记录存储该线程的ID,当这个线程再次访问同一个synchronized方法时,他会检查这个对象的Mark Word的偏向锁标记位以及是否指向了其线程ID,如果是的话就就无需再去获取(Monitor),而是直接进入到该方法中。
3.由于之前第一个线程已经获取到了偏向锁,这时第二个线程又开始争抢该对象的锁,由于第一个线程已经获取到,因此它是偏向锁,而第二个线程争抢时发现该对象头中的Mark Word中已经是偏向锁,但里面的线程id不是自己的,那么它就会CAS,从而获取到锁。
4.轻量级锁升级点
4.1 如果获取锁成功,那么它会直接将Mark Word中的线程ID由第一个线程变成自己。这样对象依然会保持偏向锁的状态

  • 如果获取锁失败,则表示可能会有多个线程同时在争抢该对象的锁,那么这时候就会进行锁升级,升级为轻量级锁。
  • 如果是另外一个线程来执行这个synchronized方法,当然是争抢不到这个锁的,所以会获取锁失败。
  • 第一种情况就是如果第一个线程没有执行完synchronized方法, 正在执行。那么将这个偏向锁会被取消掉。锁会升级到轻量级锁
  • 第二个情况是如果第一个线程已经执行完这个synchronized方法,但是多个线程会竞争,偏向锁会被取消掉,直接进入这个方法。

5.重量级锁升级点
5.1若自旋失败(依然无法获取到锁),那么锁就会转化为重量级锁。在这种情况下,无法获取到锁的线程都会进入到(Monitor)即内核态。

10、ReentrantLock的加锁逻辑能简单说一下?

1.尝试获取对象的锁,如果找不到(意味着已经有其他线程持有了锁,并且未释放),那么它就会进入AQS的阻塞队列当中。
2.如果获取到,那么根据锁是公平锁还是非公平锁进行处理。
2.1如果是公平锁,那么线程会直接放置到AQS阻塞队列的末尾
2.2如果是非公平锁,那么线程会首先尝试CAS计算,如果成功则获取到锁,如果失败,则与公平锁的处理方式一致,被放到阻塞队列的末尾。
3.当锁被释放时,调用了unlock方法,那么底层会调用release方法对state成员变量的值进行减1操作,如果减1后,state不为0.
那么release操作就执行完毕,如果减一操作后,state为0,则调用LockSupport的unpark方法唤醒该线程后的等待队列中的第一个后继线程。
将其唤醒,使之能够获取到对象的锁(release时,对于公平锁与非公平锁的处理逻辑是一样的),之所以调用release方法后state的值可能不为0.
原因在于reentrantLock是可重入锁,表示线程可以多次调用lock方法,导致每调用一次,state值都会加一
4.对于ReenTrantLock来说,所谓上锁,本质上就是对AQS中的state成员变量的操作,对该成员变量+1表示上锁,对该成员变量-1表示释放锁。

11、ReentrantLock锁的主要特点?

主要特点:
可重入锁(Reentrant Lock):是一种允许同一个线程多次获得锁的锁机制。它解决了普通锁在多次尝试获取同一把锁时可能导致的死锁问题。
多次获取:同一线程可以在持有锁的情况下再次请求该锁,每次请求都会增加锁的计数,只有在释放锁的次数等于获取次数时,锁才真正被释放
避免死锁:因为同一线程可以重入,减少了因同一线程等待自己持有的锁而造成的死锁风险。

12、简述一下ReentrantReadWriteLock的读写逻辑。

ReentrantReadWriteLock读写锁。
读锁:
1.在获取读锁的时候,判断当前对象有没有写锁,如果有写锁,则直接失败。
2.如果没有写锁,则表示当前对象没有排他锁,则当前线程尝试给当前对象加锁
3.如果当前对象已经持有了该对象的锁,那么直接将读锁数量加一

写锁:
1.在获取写锁时,会尝试判读当前对象是否已经拥有了锁(读锁,写锁)如果已经拥有且持有的线程并非当前线程,直接失败
2.如果当前对象没有被加锁,那么写锁就为当前对象上锁,并且将锁的个数加1
3.将当前对象的排他锁线程持有者设为自己。

13、Java中的Synchronized关键字和ReentrantLock之间的区别?

  1. synchronized是java关键字属于JVM层面。
    (底层通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象,只有在同步块或方法中才能调用wait/notify等方法)。2.ReentrantLock是具体类(java.util.concurrent.locks.Lock)是API层面。
    3.synchronized不需要手动释放锁,代码执行完系统会自动让线程释放对锁的占用, ReentrantLock则需要手动去释放锁,若没有主动释放有可能出现死锁,也就是lock()和unlock()方法需要配合try/finally语句快来使用。
    4.等待是否可以中断:synchronized不可中断,除非抛出异常或者正常运行完成。ReentrantLock可中断:
    5.设置超时时间 tryLock(long timeout,TimeUnit unit)lockInterruptibly()代码块中调用interrupt()方法可中断
    6.加锁是否公平:synchronized是非公平锁,ReentrantLock 两者都可,默认是非公平锁(构造方法传入boolean值,true是公平锁,false是非公平锁)
    7.锁绑定多个条件Condition:ReentrantLock 用来实现分组唤醒需要唤醒的线程,可以精确唤醒,而不是像synchronized要么随机唤醒一个要么全部唤醒

14、Java编译器对锁的优化技术

锁消除技术:
JIT编译器(Just In Time编译器)可以在动态编译同步代码块时,使用一种叫逃逸分析的技术,来通过该项技术判别程序中所使用的锁对象是否被一个线程所使用,而没有散布到其他线程当中;如果情况就是这样的话,JIT编辑器在编译这个同步代码块时就不会生成synchronized关键字所标识的锁的申请与释放机器码,从而消除了锁的使用流程。

锁粗化技术:
JIT编译器在执行动态编译时,当发现前后相邻的synchronized使用的是同一个锁对象,那么他就会把这个synchronized块给合并成一个较大的同步块,这样做的好处在于线程在执行这些代码时,就无需频繁申请与释放锁了,从而达到申请与释放锁一次,就可以执行完全部的同步代码块。从而提升了性能。

15、简述CountDownLatch的底层运行逻辑?

1.CountDownLatch用给定值的计数初始化。
2.由于其他线程调用了countDown方法,而主线程调用await方法会阻塞,直到当前计数达到零为止,在此之后,所有等待线程都会被释放,任何后续的await方法调用都会立即返回。这是一个一次性现象——计数不能被重置。

16、简述如果CyclicBarrier的底层运行逻辑?

1.初始化CyclicBarrier中各种成员变量,包括parties,count,Runnable(可选)
2.调用wait方法时,底层会先检查计数器是否已经归零,如果是的话,那么久首先执行可选的Runnable,接下来开始下一个generation
3.在下一个分代当中,将会重置count值为parties,并且创建新的generation实例。
4.同时会调用Condition的signalAll方法,唤醒所有在屏障面前等待的线程,让其开始执行。
5.如果计数器没有归零,那么当前的调用线程将会通过Condition的await方法,在屏障面前进行等待。
6.以上所有流程均在lock锁的控制范围内,不会出现并发锁的情况。

17、关于AQS与Synchronized关键字的关系?

1.Synchronized关键字底层由C++实现,存在两个重要的数据结构(集合)WaitSet和EntryList;
2.WaitSet存放的是调用了Object对象的wait方法的线程对象(被封装成了c++的Node对象);
3.EntryList存放的是陷入到阻塞状态,需要获取monitor的线程对象;
4.当一个线程被notify后,他就会从WaitSet中移动到EntryList中;
5.移动到EntryList后,但是它并没有获取到锁。依然需要与其他线程争抢monitor。
6.如果争抢到了,就表示该线程获取到了对象的锁,他就可以以排他的方式执行对应的同步代码块。

1.AQS中存在两种队列,分别是Condition对象上的条件队列,以及AQS本身的阻塞队列;
2.这两个队列中的每一个对象都是Node实例(里面封装了线程对象),
3.当位于Condition条件队列中的线程被其他线程signal后,(如果是公平锁)该线程就会从条件队列中移动到AQS的阻塞队列中;
4.位于AQS阻塞队列中的Node对象本质上都是由一个双向链表构成的;
5.在获取AQS锁时,这些进入到阻塞队列中的线程会按照队列中的排序先后尝试获取。(因为存在非公平锁)
6.当AQS阻塞队列中的线程获取到锁后,就表示该线程已经可以正常执行了。
7.陷入到等待,阻塞状态的线程,依然需要进入操作系统的内核态,进入阻塞(park方法实现)

18、为什么使用线程池?

在开发过程中,根据提交过来的请求任务进行创建线程,销毁线程,线程的创建和销毁都要消耗系统资源,当并发非常高的时候,频繁的创建和销毁线程非常消耗系统的资源。所以使用线程池,维护多个线程,这些线程在程序运行期间可以多次复用。当任务提交过来以后,就可以用已有的线程去执行任务,当线程执行完之后,线程又回到线程池,供其他任务使用。

19、Java JDK中提供的线程池有哪些?

newFixedThreadPool:创建一个固定数量大小的线程池。
newSingleThreadExecutor:创建只有一个唯一线程的线程池
newCachedThreadPool:根据提交的任务动态的调整线程池中线程数量.上限位整数INT值的上限.

这些自带的线程池可定制性差,线程拒绝策略无法修改。
newFixedThreadPool使用的阻塞队列是无界LinkedBlockingQueue,如果并发特别大,会堆积特别多的任务。

20、线程池有那些参数,该如何配置?

1、int corePoolSize:线程池当中所一直维护的线程数量,如果线程池处于空闲期间,这些线程也不会被回收掉,设置值的时候参考cpu的核心数,或者cpu超线程之后的核心数量,或者cpu超线程之后的核心数量的两倍
2、int maximumPoolSize:线程池中所维护的线程数的最大数量.超过以后就不会在创建线程了.
3、long keepAliveTime:保持存活的时间,如果线程池中的线程数量超过了corePoolSize的这些线程将会在keepAliveTime时间之后如果一致处于空闲状态,那么超过的这部分线程会被回收掉.
4、TimeUnit unit:指的是keepAliveTime的时间单位.
5、BlockingQueue workQueue:向线程池提交的任务位于的阻塞队列.它的实现有多种,例如:LikedBlockQueue,ArrayBlockQueue,PriorityBlockQueue
6、ThreadFactory threadFactory:线程工厂,用于创建新的线程并被线程池所管理,默认线程工厂所创建的线程都是用户线程且优先级为正常优先级.优先级为5
7、RejectedExecutionHandler handler:拒绝执行处理器

21、线程池拒绝策略?

7、RejectedExecutionHandler handler:拒绝执行处理器
AbortPolicy:直接抛出一个运行期异常。
DiscardPolicy:默默的丢弃掉提交的任务,什么都不做且不抛出异常
DiscardOldestPolicy:丢弃掉阻塞队列中存放时间最久的任务,也就是对头元素,并且为当前所提交的任务留出一个队列中的空闲空间,以便将其放入到队列中
CallerRunsPolicy:直接由提交任务的线程来运行这个提交任务。

22、向线程池提交任务有哪些方法?

  • 对于线程池来说,其提供了execute与submit两种方式来向线程池提交任务
  • 总体来说,submit方法可以取代execute的方法,因为它既可以接收Callable任务,也可以接收Runnable任务。
  • submit有三种方式,无论哪种方式,最终都是将传递进来的任务转换为一个Callable对象进行处理。
  • 当Callable对象构造完毕后,最后都会调用Executor接口中声明的execute方法进行统一的处理。

23、关于线程池的总体执行策略

1.如果线程池中正在执行的线程数 < corePoolSize,那么线程池就会优先选择创建新的线程而不是将提交的任务加到阻塞队列中。
2.如果线程池中正在执行的线程数 >= corePoolSize,那么线程池就会优先选择对提交的任务阻塞排队而非创建新的线程。
3.如果提交的任务无法加入到阻塞队列当中,那么线程池就会创建新的线程,从阻塞队列拉取任务执行,如果创建的线程数超过了maximumPoolSize,那么拒绝策略就会起作用。

24、线程池状态

RUNNING:表示线程池可以接收新的任务提交,并且还可以处理阻塞队列中的任务。
SHUTDOWN:表示不再接收新的任务提交,不过线程池可以继续去处理阻塞队列中任务。
STOP:不再接收新的任务,同时还会丢弃阻塞队列中的既有任务。此外,它还会中断处理中的任务。
TIDYING:所有的任务都执行完毕后,当线程池中的活动线程的数量降为0,将会调用terminated。
TERMINATED:线程池的终止状态,当terminated方法执行完毕后,线程池将会处于该状态之下。

25、线程池状态变迁

RUNNING -> SHUTDOWN
当调用了线程池的shutdown方法。或者当finalized方法被隐士调用以后(该方法内部会调用shutdown方法)。
RUNNING,SHUTDOWN -> STOP
当调用了线程池的shutdownNow方法时,同时丢弃阻塞队列的任务。
SHUTDOWN -> TIDYING
在线程池与阻塞队列均变成空时。
STOP -> TIDYING
在线程池变为空时。
TIDYING -> TERMINATED
当terminated方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值