想自己做个网站在哪里做,有什么网站可以做编程题,如何用手机制作网站,蚌埠市做家教网站接着之前的Java-并发高频面试题
7. synchronized的实现原理是怎么样的#xff1f;
首先我们要知道synchronized它是解决线程安全问题的一种方式#xff0c;而具体是怎么解决的呢#xff1f;主要是通过加锁的方式来解决 在底层实现上来看 是通过 monitorenter、monitorexit…接着之前的Java-并发高频面试题
7. synchronized的实现原理是怎么样的
首先我们要知道synchronized它是解决线程安全问题的一种方式而具体是怎么解决的呢主要是通过加锁的方式来解决 在底层实现上来看 是通过 monitorenter、monitorexit两个指令来实现同步monitorenter 指令指向同步代码块的开始位置 monitorexit 指令则指向同步代码块的结束位置。
那么synchronized是如何保证原子性的呢 synchronized保证原子性主要就是通过上面说的monitorenter、monitorexit两个指令当执行到monitoreter的时候要先获得锁而执行monitorexit的时候则要释放锁 这样一来通过两个指令就可以保证被synchronized修饰的代码同一时间只能被一个线程访问在锁未释放之前无法被其他线程访问到。所以说synchronized是可以保证原子性的
那么synchronized是如何保证可见性的呢
线程加锁前将清空工作内存中共享变量的值从而使用共享变量时需要从主内存中重新读取最新的值。线程加锁后其它线程无法获取主内存中的共享变量。线程解锁前必须把共享变量的最新值刷新到主内存中。 简单说来就是被synchronized修饰的代码为了保证可见性它在对变量解锁之前会强制将变量的值同步回主内存中这样在解锁之后其他线程也就可以访问到被修改后的值
那么synchronized是如何保证有序性的呢 首先我们要知道有序性的定义就是按照代码的先后顺序来去执行那么synchronized基于monitorenter 和monitorexit加锁后本身就是会单个线程来去运行而按照as-if-sieal语义来看编译器和执行器即便在进行指令重排 也必须要保证单线程的执行结果不能改变 所以总结一下:因为被synchronized修饰的代码同一个时间只能被一个线程访问也就是单线程执行的所以是天然可以保证有序性的
8. 锁升级synchronized锁优化介绍下
如果要想清楚锁升级需要先知道不同锁的状态这个锁状态其实就是java对象头中的mark word 标记字段而这块结构会随着锁的状态的变化而变化。 如下图所示 再具体细看 Mark Word存储的是对象自身的 运行数据如哈希码、GC分代年龄、锁状态标志、偏向时间戳Epoch 等信息。 而从上图也可以看出因为mark word状态的不同又可以区分出无锁偏向锁轻量级锁重量级锁我们依次介绍下吧 无锁简单来说就是mark word中锁状态为01时同时在低三位不是偏向锁的状态 偏向锁就是mark word 中锁状态为01 同时低三位是偏向锁状态 轻量级锁mark word中锁状态为00标志 重量级锁mark word中锁状态为10标志
那么他们之间是怎么转化升级的呢 锁升级方向无锁–偏向锁— 轻量级锁----重量级锁这个方向基本上是不可逆的。
首先呢其实是无锁状态 也就是不加锁 之后当一个线程第一次访问对象时jvm它其实会在对象头中设置该线程的thread ID,然后将对象的状态位设置为偏向锁设置成偏向锁以后如果有其他线程来访问该对象那么就会检查该对象的偏向锁状态标志如果说和自己的线程ID相同那么就会直接获取到锁而如果不同那么偏向锁就会升级为轻量级锁状态而轻量级锁下jvm会将mark word复制一份到线程栈中并在对象头中存储线程栈的指针。 然后如果有其他线程来访问该对象时会发现该对象已经处于轻量级锁状态那么就会尝试用cas操作将对象头中的指针替换成自己的指针。如果能够替换成功也就会获取到锁如果替换失败表示已经有其他线程获取了锁那么这个轻量级锁就会升级为重量级锁。 如果锁一旦升级为重量级锁其实也就是最笨重原始的加锁会基于objectMonitor中的等待队列排队加锁并会在对象头中记录指向等待队列的指针也就是说在这种情况下 如果一个线程想要加锁就需要先进入等待队列等待锁被释放当锁被释放以后jvm会从等待队列中选择一个线程来唤醒然后线程会进入就绪状态等待重新获取对象的锁
9.synchronized和ReentrantLock的区别有哪些
其实大致可以从锁的实现使用性能功能特点来展开去描述 锁的实现synchronized是关键字是基于JVM来去实现的而ReentrantLock是类是基于java api来去实现的 锁的使用synchronized作为关键字 是可以修饰方法和同步代码块会自动释放锁而ReentrantLock 作为类是通过lock、unlock方法来完成加锁和解锁需要手动释放锁。 性能synchronized在早期版本中性能不如ReentrantLock但是在1.6后增加了适应性自旋、锁消除等两者性能就差不多了 功能特点Reentrantlock支持公平锁和非公平锁但是synchronized是非公平锁即线程是否要排队加锁 synchronized与wait()和notify()/notifyAll()方法结合实现等待/通知机制ReentrantLock类借助Condition接口与newCondition()方法实现。 ReentrantLock提供了能够中断等待锁的线程机制即通过lock.lockInterruptibly()来实现该机制
10.什么是AQS?
简单来说AQS 就是抽象队列同步器是java并发编程底层的核心实现类 AQS 的核心思想是利用一个双向队列来保存等待锁的线程同时利用一个 state 变量来表示锁的状态。AQS 的同步器可以分为独占模式和共享模式两种。 独占模式是指同一时刻只允许一个线程获取锁常见的实现类有 ReentrantLock 共享模式是指同一时刻允许多个线程同时获取锁常见的实现类有 Semaphore、CountDownLatch、CyclicBarrier 等。 我们首先看下独占模式的原理 简单说就是独占模式下AQS 维护了一个同步队列和一个state变量队列中会保存了所有等待获取锁的线程。如果有多个线程同时再执行加锁操作时会通过cas操作才更新AQS内部的state的值如果某一个线程加锁成功会将state的值更改为1同时aqs中会指定加锁线程为当前线程与此同时别的线程加锁失败会进入等待队列 一旦加锁线程释放锁就会将state的值设为0同时将加锁线程更新为null,而加锁线程也会通知排在等待队列中的头元素 同时 独占模式又分为公平锁和非公平锁默认非公平锁 公平锁按照线程在队列中的排队顺序先到者先拿到锁 非公平锁当线程要获取锁时无视队列顺序直接去抢锁谁抢到就是谁的。
共享模式的原理 AQS 维护了一个等待队列和一个共享计数器。共享计数器表示当前允许获取锁的线程数当一个线程尝试获取锁时如果当前允许获取锁的线程数已经达到了最大值则将该线程加入到等待队列中并挂起线程等待其他线程释放锁或者共享计数器增加。 当锁被释放时会从等待队列中取出一个线程使其获取锁同时将它从队列中移除唤醒该线程继续执行。
11 保证线程安全的手段有哪些?
在 Java 中多线程并发操作同一个共享变量时就可能会发生线程安全问题。 在 Java 中保证线程安全的常用手段有以下三个 锁机制在 Java 中锁机制主要有两种synchronized 关键字和 Lock 接口。synchronized 关键字是 Java 中最基本的锁机制它可以用来修饰方法或代码块以实现对共享资源的互斥访问。而 Lock 接口是 Java5 中新增的一种锁机制它提供了比 synchronized 更强大、更灵活的锁定机制例如可重入锁、读写锁等
使用线程安全的容器如 **ConcurrentHashMap、Hashtable、Vector。**需要注意的是线程安全的容器底层通常也是使用锁机制实现的
使用本地变量ThreadLocal线程本地变量是一种特殊的变量它只能被同一个线程访问。在 Java 中线程本地变量可以通过 ThreadLocal 类来实现。每个 ThreadLocal 对象都可以存储一个线程本地变量而且每个线程都有自己的一份线程本地变量副本因此不同的线程之间互不干扰。
12 ThreadLocal是什么
ThreadLocal也就是线程本地变量。如果你创建了一个ThreadLocal变量那么访问这个变量的每个线程都会有这个变量的一个本地拷贝多个线程操作这个变量的时候实际是操作自己本地内存里面的变量从而起到线程隔离的作用避免了线程安全问题。 而ThreadLocal用起来并不麻烦就是声明该变量然后通过set 写入和get获取就可以进行操作
那你在工作中有用到ThreadLocal吗 threadLocal的场景很多 比如说可以基于threadlocal做用户信息的上下文存储以及cookiesession等的隔离还有比如数据库连接池 可以交给ThreadLoca进行管理保证当前线程的操作都是同一个Connnection
ThreadLocal怎么实现的呢实现原理是啥 其实TheadLocal本身并不存储数据真正存储数据的是Threadlocalmap, 也就是说每个线程都会有一个属于自己的ThreadLocalMap 而ThreadLocalMap内部维护着Entry数组每个Entry代表一个完整的对象key是ThreadLocal的弱引用value是ThreadLocal的泛型值。 每个线程在往ThreadLocal里设置值的时候都是往自己的ThreadLocalMap里存读也是以某个ThreadLocal作为引用在自己的map里找对应的key从而实现了线程隔离。
ThreadLocal 内存泄露是怎么回事 因为 ThreadLocalMap中使用的 key 为 ThreadLocal 的弱引用。 而“弱引用的定义是 只要垃圾回收机制一运行不管JVM的内存空间是否充足都会回收该对象占用的内存。” 所以说 弱引用很容易被回收如果ThreadLocalThreadLocalMap的Key被垃圾回收器回收了但是ThreadLocalMap生命周期和Thread是一样的它这时候如果不被回收就会出现这种情况ThreadLocalMap的key没了value还在这就会造成了内存泄漏问题。
那怎么解决内存泄漏问题呢 其实 很简单使用完ThreadLocal后及时调用remove()方法释放内存空间。
那为什么key还要设计成弱引用
key设计成弱引用同样是为了防止内存泄漏。
假如key被设计成强引用如果ThreadLocal 的引用即指针被销毁此时它指向ThreadLoca的强引用就没有了但是此时key还强引用指向ThreadLocal就会导致ThreadLocal不能被回收这时候就发生了内存泄漏的问题。
13 线程死锁的条件有哪些
死锁的产生需要满足以下 4 个条件 互斥条件也就是说在一段时间内某个锁资源只能被一个运算单元所占用。
请求和保持条件说白了就是当前线程A占用着某个资源a但是又去请求其他资源b而资源b又恰好被其他线程B占用着不释放就是处于这种请求保持条件
不可剥夺条件指运算单元已获得的资源在未使用完之前不能被剥夺。
环路等待条件指在发生死锁时必然存在运算单元和资源的环形链即运算单元正在等待另一个运算单元占用的资源而对方又在等待自己占用的资源从而造成环路等待的情况。 只有以上 4 个条件同时满足才会造成死锁。
死锁的常用解决方案有以下两个 按照顺序加锁尝试让所有线程按照同一顺序获取锁从而避免死锁。 设置获取锁的超时时间尝试获取锁的线程在规定时间内没有获取到锁就放弃获取锁避免因为长时间等待锁而引起的死锁。
死锁排查工具有哪些 常见的工具有以下几个 jstack可以查看 Java 应用程序的线程状态和调用堆栈可用于发现死锁线程的状态。 jconsole 和 JVisualVM这些是 Java 自带的监视工具可以用于监视线程、内存、CPU 使用率等信息从而帮助排查死锁问题。
14 CAS了解吗有哪些实现以及存在哪些问题
CAS叫做CompareAndSwap⽐较并交换主要是通过处理器的指令来保证操作的原⼦性的。 CAS 指令包含 3 个参数共享变量的内存地址 A、预期的值 B 和共享变量的新值 C。 只有当内存中地址 A 的值等于 B 时才能将内存中地址 A 的值更新为新值 C。作为一条 CPU 指令CAS 指令本身是能够保证原子性的
具体实现在 Java 中CAS 操作被封装在 Atomic 类中例如 AtomicInteger 类就是利用了 CAS 操作来实现线程安全的自增操作。同时Java 还提供了一些工具类来支持 CAS 操作例如 Unsafe 类它提供了一些原始的 CAS 操作方法供 JVM 内部使用
CAS存在的问题 1. ABA问题 并发环境下假设初始条件是A去修改数据时发现是A就会执行修改。但是看到的虽然是A中间可能发生了A变BB又变回A的情况 那该如何解决ABA问题呢 加版本号或者时间戳说白了就是每次修改变量都在这个变量的版本号上加1这样刚刚A-B-A虽然A的值没变但是它的版本号已经变了再判断版本号就会发现此时的A已经被改过了。参考乐观锁的版本号这种做法可以给数据带上了一种实效性的检验。 Java提供了AtomicStampReference类它的compareAndSet方法首先检查当前的对象引用值是否等于预期引用并且当前印戳Stamp标志是否等于预期标志如果全部相等则以原子方式将引用值和印戳标志的值更新为给定的更新值。
2. 只能保证一个变量的原子操作 CAS 保证的是对一个变量执行操作的原子性如果对多个变量操作时CAS 目前无法直接保证操作的原子性的。
怎么解决只能保证一个变量的原子操作问题 可以考虑改用锁来保证操作的原子性 可以考虑合并多个变量将多个变量封装成一个对象java中可以用AtomicReference来保证原子性
3. 无限循环导致性能降低 自旋CAS如果一直循环执行一直不成功会给CPU带来非常大的执行开销。 那么如何来解决循环问题呢可以通过设置自旋次数即超过一定次数就停止自旋
15. AtomicInteger 的原理
说白了 就是基于CAS来实现的在底层是用Unsafe类的实例来进行添加操作而Unsafe类中的compareAndSwapInt其实就是一个native方法基于CAS来操作int类型变量。
public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 var4));return var5;
}