Java锁和并发

Synchronized

Synchronized实现原理

通过加锁来实现同步,在编译之后,通过在代码块前后加monitor enter和exit来实现;

分别修饰代码块、修饰方法、修饰静态方法,加锁的对象。

每个对象都是由对象头、对象实际数据、填充组成的。

对象头中的MarkWord会记录锁的信息,对于不同的锁格式不相同。

如果是重量级锁的话,存储了指向重量级锁的指针。偏向锁就是存储了线程ID等信息。

轻量级锁指向线程中markword副本。

Synchronized如何实现可重入

每个锁对象内部维护一个计数器,该计数器初始值为0,表示任何线程都可以获取该锁并执行相应的方法。根据虚拟机规范的要求,在执行monitorenter指令时,首先要尝试获取对象的锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1,相应的,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁就被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。

ReentrantLock如何实现可重入

ReentrantLock在内部使用了内部类Sync来管理锁,所以真正的获取锁是由Sync的实现类控制的。Sync有两个实现,分别为NonfairSync(非公平锁)和FairSync(公平锁)。Sync通过继承AQS实现,在AQS中维护了一个private volatile int state来计数重入次数,避免了频繁的持有释放操作带来效率问题。3个状态:没占用是0,占用了是1,大于1是可重入锁

AQShttps://javadoop.com/post/AbstractQueuedSynchronizer

synchronized 和 Lock 有什么区别?

synchronized 和 ReentrantLock 区别是什么?

synchronized锁升级的原理?

在无锁状态下markword中偏向标志为0,如果这时有线程请求锁并且设置了可以是偏向锁就将偏向标志设为1 .线程ID设置为当前线程。如果这时候另外一个线程请求锁,锁就会升级为轻量级锁,markword中放着指向线程栈中markword副本的指针,这时候会自旋。如果自旋的线程过多就会升级为重量级锁。

四种引用

强引用:

软引用:SoftReference

弱引用:WeakReference

虚引用:

哪里使用了弱引用?

ThreadLocal

ThreadLocal

通过ThreadLocal来存储当前线程需要存储变量的一个副本。但实际上ThreadLocal是把这个(它自己,变量)存在了Thread的ThreadLocaMap中,然后通过该ThreadLocal取取值。

每一个Thread中都有一个ThreadLocalMap,ThreadLocalMap是ThreadLOcal的内部类,ThreadLocalMap里由一个Entry数组实现。

一个Thread,通过ThreadLocal对象来保存一个值,每个ThreadLocal是键,对应着一个值。如果要保存两个变量就需要两个threadlocal。

ThreadLocal为什么会造成内存泄漏?

因为ThreadLocalMap是在Thread中,ThreadLocalMap中某个V的K指向了ThreadLocal对象,但这个对象是弱引用WeakReference,如果该对象的没有其他强引用之后该对象将被回收。此时Thread将无法获得该V,但是它永远都在Thread的ThreadLocalMap的Entry数组中。

ThreadLocal正确的使用方法

  • 每次使用完ThreadLocal都调用它的remove()方法清除数据
  • 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。

死锁

是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现

悲观锁

当我们要对一个数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。

悲观锁的实现,往往依靠数据库提供的锁机制。Synchronized java;数据库的页锁和行锁。

select…for update 会锁住数据 如果用到了索引是 行锁;没用到索引是表锁。

乐观锁

乐观锁( Optimistic Locking ) 是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。CAS+MVCC

Java中AtomicInteger等

CAS操作中包括三个操作数:内存中的值,期望的值,修改后的值。每次提交修改后的值要检查期望值于内存中的值是否相同,不相同会失败;相同则成功。

https://www.cnblogs.com/kyoner/p/11318979.html

锁优化

如适应性自旋(Adaptive Spinning)、锁消除(Lock Elimination)、锁膨胀(Lock Coarsening)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)等,这些技术都是为了在线程之间更高效地共享数据及解决竞争问题,从而提高程序的执行 效率。

自旋锁

互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢 复线程的操作都需要转入内核态中完成,这些操作给Java虚拟机的并发性能带来了很大的压力。所以如果处理器有两个以上的核心九合一让后面请求锁的线程多等一会而不是加入阻塞队列然后挂起。多等一会就是通过自旋(忙循环)来实现。

但是自旋锁会占用处理器时间,所以需要设定一个自旋次数,默认十次,超过后就挂起。

适应性自旋

自旋的时间不是固定的,是由前一次在同一个锁上的自选时间以及锁的拥有者的状态来确定的。

锁消除

一些代码要求了同步,但是检测不到存在的数据竞争,就可以将这个锁来消除,主要是通过逃逸分析的数据支持来判断。

锁粗化

如果一系列的操作对同一个对象进行反复加锁和解锁,甚至在循环体中,那么锁的范围会扩展到整个操作序列的外部。

轻量级锁

image-20210416194929575

MarkWord一共32位,在不同的锁状态下有不同的结构

未锁定和可偏向差不多 标志位都为01 偏向模式不同 可偏向下有一个线程ID指向偏向的线程

轻量级锁下30位用来存储指向调用栈中锁记录的指针。每个线程在竞争锁的时候都会在本地栈帧放一下对象头,然后CAS尝试修改对象的对象头,如果失败就看一下对象的对象头是否指向自己。

持有偏向锁的线程访问这个对象不会存在任何同步操作,如果访问的线程不是该偏向锁ID中的线程,那么偏向锁模式会升级为轻量锁

重量级锁下30 位指向重量级锁的指针。

CAS

https://blog.csdn.net/weixin_42636552/article/details/82383272

JUC

AQS

AbstractQuenedSynchronizer抽象的队列式同步器。是除了java自带的synchronized关键字之外的锁机制

如果当前请求的资源空闲,将当前请求的线程设置为有效的工作线程,那么将其设置为锁定状态(通过CAS修改状态)。如果请求的线程不空闲,那么通过CLH队列锁实现线程的阻塞以及唤醒。

AQS 定义了两种资源共享方式:
1.Exclusive:独占,只有一个线程能执行,如ReentrantLock
2.Share:共享,多个线程可以同时执行,如Semaphore、CountDownLatch、ReadWriteLock,CyclicBarrier

https://blog.csdn.net/TZ845195485/article/details/109210263

https://blog.csdn.net/mulinsen77/article/details/84583716

CountDownLatch

线程控制

CyclicBarrier

可重置的线程控制

Semaphore

信号量来模拟资源

Phaser

阶段性的控制

BlockingQueue