并发编程01/Synchronized/ReentrantLock

并发编程01

1.Synchronized用过吗,原理是什么

Synchronized是由JVM实现的一种实现互斥同步的一种方式,被Synchronized修饰过的程序块,在编译前后被编译器生成了montiorentermonitorexit两个字节码指令。

​ 这两个指令是什么意思呢?在虚拟机执行到montiorenter指令时,首先要尝试获取对象的锁:如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁,把计数器+1;当执行monitorexit指令时将锁计数器-1;当计数器为0时,锁就被释放了。如果获取对象失败了,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。Java中Synchronized通过在对象头设置标记,达到了获取锁和释放锁的目的。

2.提到获取对象的锁,这个“锁”是什么?如何确定对象的锁?

“锁”的本质其实是monitorentermonitorexit字节码指令的一个Reference类型的参数,即要锁定和解锁的对象。我们知道,使用Synchronized可以修饰不同的对象,因此,对应的对象锁可以这么确定。

1.如果Synchronized明确指定了锁对象,比如Synchronized(变量名)、Synchronized(this)等,说明加解锁对象为该对象

2 .如果没有明确指定:

若 Synchronized 修饰的方法为非静态方法,表示此方法对应的对象锁对象

若 Synchronized 修饰的方法为静态方法,则表示此方法对应的类对象为锁对象。注意,当一个对象被锁住时,对象里面所有用 Synchronized 修饰的方法都将产生堵塞,而对象里非 Synchronized 修饰的方法正常被调用,不受锁影响。

3 、什么是可重入性,为什么说 Synchronized 是可重入锁?

可重入性是锁的一个基本要求,是为了解决自己锁死自己的情况。

Synchronized 来说,可重入性是显而易见的,刚才提到,在执行 monitorenter 指令时,如果这个对象没有锁定,或者当前线程已经拥有了这个对象的锁(而不是已拥有了锁则不能继续获取),就把锁的计数器+ 1 , 其实本质上就通过这种方式实现了可重入性。

4 、为什么说 Synchronized 是非公平锁?

非公平主要表现在获取锁的行为上,并非是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁,这样做的目的是为了提高执行性能,缺点是可能会产生线程饥饿现象。

5、为什么说Synchronized 是一个悲观锁?乐观锁的实现原理是什么?

什么是 CAS ,它有什么特性?

Synchronized 显然是一个悲观锁,因为它的并发策略是悲观的:不管是否会产生竞争,任何的数据操作都必须要加锁、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等操作。

这种乐观的并发策略的许多实现不需要线程挂起,所以被称为非阻塞同步。乐观锁的核心算法是 CAS ( Compare and Swap ,比较并交换),它涉及到三个操作数:内存值预期值新值当且仅当预期值和内存值相等时才将内存值修改为新值。这样处理的逻辑是,首先检查某块内存的值是否跟之前我读取时的一样,如不一样则表示期间此内存值已经被别的线程更改过,舍弃本次操作,否则说明期间没有其他线程对此内存值操作,可以把新值设置给此块内存。 CAS 具有原子性,它的原子性由 CPU 硬件指令实现保证,即使用 JNI 调用 Native 方法调用由 C ++编写的硬件级别指令, JDK 中提供了 Unsafe 类执行这些操作。

6 、乐观锁一定就是好的吗?

乐观锁避免了悲观锁独占对象的现象,同时也提高了并发性能但它也有缺点:

1 .乐观锁只能保证一个共享变量的原子操作。如果多一个或几个变量,乐观锁将变得力不从心,但互斥锁能轻易解决,不管对象数量多少及对象颗粒度大小。

2 .长时间自旋可能导致开销大。假如 CAS 长时间不成功而一直自旋,会给 CPU 带来很大的开销。

3 . ABA 问题。 CAS 的核心思想是通过比对内存值与预期值是否一样而判断内存值是否被改过,但这个判断逻辑不严谨,假如内存值原来是 A ,后来被一条线程改为 B ,最后又被改成了 A ,则 CAS 认为此内存值并没有发生改变,但实际上是有被其他线程改过的,这种情况对依赖过程值的情景的运算结果影晌很大。解决的思路是引入版本号,每次变量更新都把版本号加一

7 、跟 Synchronized 相比,可重入锁 ReentrantLock 其实现原理有什么不同?

其实,锁的实现原理基本是为了达到一个目的:让所有的线程都能看到某种标记。

Synchronized 通过在对象头中设置标记实现了这一目的,是一种 JVM 原生的锁实现方式,而 ReentrantLock 以及所有的基于 Lock 接口的实现类,都是通过用一个 volitile 修饰的 int 型变量,并保证每个线程都能拥有对该 int 的可见性原子修改,其本质是基于所谓的 AQS用来构建锁和同步器的框架)框架。

AQS(AbstractQueuedSynchronizer 类)在内部定义了一个volatile int state 变量,表示同步状态:当线程调动lock方法时,如果state=0,说明没有任何线程占用共享资源的锁,可以获得锁并将 state=1;如果state=1,则说明有线程目前正在使用共享变量,其他线程必须加入同步队列进行等待。

8、Synchronized 和 ReentrantLock 的异同。

ReentrantLock 是 Lock 的实现类是一个互斥同步锁。从功能角度, ReentrantLock 比 Synchronized 的同步操作更精细(因为可以像普通对象一样使用),甚至实现 Synchronized 没有的高级功能,如:

①、等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,对处理执行时间非常长的同步块很有用

②、带超时的获取锁尝试:在指定的时间范围内获取锁,如果时间到了仍然无法获取则返回。

③、可以判断是否有线程在排队等待获取锁

④、可以响应中断请求:与 Synchronized 不同,当获取到锁的线程被中断时,能够响应中断,中断异常将会被抛出,同时锁会被释放。

⑤、可以实现公平锁

从锁释放角度, Synchronized 在 JVM 层面上实现的,不但可以通过一些监控工具监控 Synchronized 的锁定,而且在代码执行出现异常时, JVM 会自动释放锁定;但是使用 Lock 则不行,Lock 是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock() 放到 **finally{ }**中。

从性能角度, Synchronized早期实现比较低效,对比 ReentrantLock ,大多数场景性能都相差较大。但是在 Java6 中对其进行了非常多的改进:

在竞争不激烈时Synchronized性能要优于 ReetrantLock ;

在高竞争情况下, Synchronized 的性能会下降几十倍,但是 ReetrantLock 的性能能维持常态。

9、ReentrantLock 是如何实现可重入性的?

ReentrantLock 内部自定义了同步器 Sync ( Sync 既实现了 AQS ,又实现了 AOS ,而 AOS 提供了一种互斥锁持有的方式),其实就是加锁的时候通过 CAS 算法,将线程对象放到一个双向链表中,每次获取锁的时候,看下当前维护的那个线程 ID 和当前请求的线程旧是否一样,一样就可重入了

10、Java 中的线程池是如何实现的?

在 Java 中,所谓的线程池中的“线程”,其实是被抽象为了一个静态内部类 Worker ,它基于 AQS 实现,存放在线程池的 HashSet workers 成员变量中;而需要执行的任务则存放在成员变量 workQueue ( BlockingQueue workQueue )中。

这样,整个线程池实现的基本思想就是:从 workQueue 中不断取出需要执行的任务,放在 Workers 中进行处理。

11 、创建线程池的几个核心构造参数?

Java 中的线程池的创建其实非常灵活,我们可以通过配置不同的参数,创建出行为不同的线程池,这几个参数包括:

corePooISize :线程池的核心线程数maximumPoolSize :线程池允许的最大线程数

keepAliveTime超过核心线程数时闲置线程的存活时间

workQueue任务执行前保存任务的队列保存由 execute 方法提交的 Runnable 任务

12、线程池中的线程是怎么创建的?

是一开始就随着线程池的启动创建好的吗?显然不是的。线程池默认初始化后不启动 Worker ,等待有请求时才启动。

每当我们调用 execute() 方法添加一个任务时,线程池会做如下判断:

①、如果正在运行的线程数量小于 corePooISize ,那么马上创建线程运行这个任务;

②、如果正在运行的线程数量大于或等于 corePooISize ,那么将这个任务放入队列;

③、如果这时候队列满了,而且正在运行的线程数量小于 maximumPooISize ,那么还是要创建非核心线程立刻运行这个任务;

④、如果队列满了,而且正在运行的线程数量大于或等于 maximumPooISize ,那么线程池会抛出异常 RejectExecutionException **。当一个线程完成任务时,它会从队列中取下一个任务来执行。当一个线程无事可做,超过一定的时间( keepAliveTime )时,**线程池会判断。如果当前运行的线程数大于 corePooISize ,那么这个线程就被停掉所以线程池的所有任务完成后,它最终会收缩到 corePooISize 的大小。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值